View Javadoc

1   /***
2    * Licensed under the Artistic License; you may not use this file
3    * except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    *      http://displaytag.sourceforge.net/license.html
7    *
8    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
9    * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10   * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11   */
12  package org.displaytag.properties;
13  
14  import java.io.IOException;
15  import java.util.Enumeration;
16  import java.util.HashMap;
17  import java.util.Locale;
18  import java.util.Map;
19  import java.util.MissingResourceException;
20  import java.util.Properties;
21  import java.util.ResourceBundle;
22  
23  import javax.servlet.http.HttpServletRequest;
24  import javax.servlet.jsp.PageContext;
25  import javax.servlet.jsp.tagext.Tag;
26  
27  import org.apache.commons.lang.ClassUtils;
28  import org.apache.commons.lang.StringUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.displaytag.Messages;
32  import org.displaytag.exception.FactoryInstantiationException;
33  import org.displaytag.exception.TablePropertiesLoadException;
34  import org.displaytag.exception.WrappedRuntimeException;
35  import org.displaytag.localization.I18nResourceProvider;
36  import org.displaytag.localization.LocaleResolver;
37  import org.displaytag.util.DefaultRequestHelperFactory;
38  import org.displaytag.util.ReflectHelper;
39  import org.displaytag.util.RequestHelperFactory;
40  
41  
42  /***
43   * The properties used by the Table tags. The properties are loaded in the following order, in increasing order of
44   * priority. The locale of getInstance() is used to determine the locale of the property file to use; if the key
45   * required does not exist in the specified file, the key will be loaded from a more general property file.
46   * <ol>
47   * <li>First, from the TableTag.properties included with the DisplayTag distribution.</li>
48   * <li>Then, from the file displaytag.properties, if it is present; these properties are intended to be set by the user
49   * for sitewide application. Messages are gathered according to the Locale of the property file.</li>
50   * <li>Finally, if this class has a userProperties defined, all of the properties from that Properties object are
51   * copied in as well.</li>
52   * </ol>
53   * @author Fabrizio Giustina
54   * @author rapruitt
55   * @version $Revision: 1.35 $ ($Author: fgiust $)
56   */
57  public final class TableProperties implements Cloneable
58  {
59  
60      /***
61       * name of the default properties file name ("TableTag.properties").
62       */
63      public static final String DEFAULT_FILENAME = "TableTag.properties"; //$NON-NLS-1$
64  
65      /***
66       * The name of the local properties file that is searched for on the classpath. Settings in this file will override
67       * the defaults loaded from TableTag.properties.
68       */
69      public static final String LOCAL_PROPERTIES = "displaytag"; //$NON-NLS-1$
70  
71      /***
72       * property <code>export.banner</code>.
73       */
74      public static final String PROPERTY_STRING_EXPORTBANNER = "export.banner"; //$NON-NLS-1$
75  
76      /***
77       * property <code>export.banner.sepchar</code>.
78       */
79      public static final String PROPERTY_STRING_EXPORTBANNER_SEPARATOR = "export.banner.sepchar"; //$NON-NLS-1$
80  
81      /***
82       * property <code>export.decorated</code>.
83       */
84      public static final String PROPERTY_BOOLEAN_EXPORTDECORATED = "export.decorated"; //$NON-NLS-1$
85  
86      /***
87       * property <code>export.amount</code>.
88       */
89      public static final String PROPERTY_STRING_EXPORTAMOUNT = "export.amount"; //$NON-NLS-1$
90  
91      /***
92       * property <code>sort.amount</code>.
93       */
94      public static final String PROPERTY_STRING_SORTAMOUNT = "sort.amount"; //$NON-NLS-1$
95  
96      /***
97       * property <code>basic.show.header</code>.
98       */
99      public static final String PROPERTY_BOOLEAN_SHOWHEADER = "basic.show.header"; //$NON-NLS-1$
100 
101     /***
102      * property <code>basic.msg.empty_list</code>.
103      */
104     public static final String PROPERTY_STRING_EMPTYLIST_MESSAGE = "basic.msg.empty_list"; //$NON-NLS-1$
105 
106     /***
107      * property <code>basic.msg.empty_list_row</code>.
108      */
109     public static final String PROPERTY_STRING_EMPTYLISTROW_MESSAGE = "basic.msg.empty_list_row"; //$NON-NLS-1$
110 
111     /***
112      * property <code>basic.empty.showtable</code>.
113      */
114     public static final String PROPERTY_BOOLEAN_EMPTYLIST_SHOWTABLE = "basic.empty.showtable"; //$NON-NLS-1$
115 
116     /***
117      * property <code>paging.banner.placement</code>.
118      */
119     public static final String PROPERTY_STRING_BANNER_PLACEMENT = "paging.banner.placement"; //$NON-NLS-1$
120 
121     /***
122      * property <code>error.msg.invalid_page</code>.
123      */
124     public static final String PROPERTY_STRING_PAGING_INVALIDPAGE = "error.msg.invalid_page"; //$NON-NLS-1$
125 
126     /***
127      * property <code>paging.banner.item_name</code>.
128      */
129     public static final String PROPERTY_STRING_PAGING_ITEM_NAME = "paging.banner.item_name"; //$NON-NLS-1$
130 
131     /***
132      * property <code>paging.banner.items_name</code>.
133      */
134     public static final String PROPERTY_STRING_PAGING_ITEMS_NAME = "paging.banner.items_name"; //$NON-NLS-1$
135 
136     /***
137      * property <code>paging.banner.no_items_found</code>.
138      */
139     public static final String PROPERTY_STRING_PAGING_NOITEMS = "paging.banner.no_items_found"; //$NON-NLS-1$
140 
141     /***
142      * property <code>paging.banner.one_item_found</code>.
143      */
144     public static final String PROPERTY_STRING_PAGING_FOUND_ONEITEM = "paging.banner.one_item_found"; //$NON-NLS-1$
145 
146     /***
147      * property <code>paging.banner.all_items_found</code>.
148      */
149     public static final String PROPERTY_STRING_PAGING_FOUND_ALLITEMS = "paging.banner.all_items_found"; //$NON-NLS-1$
150 
151     /***
152      * property <code>paging.banner.some_items_found</code>.
153      */
154     public static final String PROPERTY_STRING_PAGING_FOUND_SOMEITEMS = "paging.banner.some_items_found"; //$NON-NLS-1$
155 
156     /***
157      * property <code>paging.banner.group_size</code>.
158      */
159     public static final String PROPERTY_INT_PAGING_GROUPSIZE = "paging.banner.group_size"; //$NON-NLS-1$
160 
161     /***
162      * property <code>paging.banner.onepage</code>.
163      */
164     public static final String PROPERTY_STRING_PAGING_BANNER_ONEPAGE = "paging.banner.onepage"; //$NON-NLS-1$
165 
166     /***
167      * property <code>paging.banner.first</code>.
168      */
169     public static final String PROPERTY_STRING_PAGING_BANNER_FIRST = "paging.banner.first"; //$NON-NLS-1$
170 
171     /***
172      * property <code>paging.banner.last</code>.
173      */
174     public static final String PROPERTY_STRING_PAGING_BANNER_LAST = "paging.banner.last"; //$NON-NLS-1$
175 
176     /***
177      * property <code>paging.banner.full</code>.
178      */
179     public static final String PROPERTY_STRING_PAGING_BANNER_FULL = "paging.banner.full"; //$NON-NLS-1$
180 
181     /***
182      * property <code>paging.banner.page.link</code>.
183      */
184     public static final String PROPERTY_STRING_PAGING_PAGE_LINK = "paging.banner.page.link"; //$NON-NLS-1$
185 
186     /***
187      * property <code>paging.banner.page.selected</code>.
188      */
189     public static final String PROPERTY_STRING_PAGING_PAGE_SELECTED = "paging.banner.page.selected"; //$NON-NLS-1$
190 
191     /***
192      * property <code>paging.banner.page.separator</code>.
193      */
194     public static final String PROPERTY_STRING_PAGING_PAGE_SPARATOR = "paging.banner.page.separator"; //$NON-NLS-1$
195 
196     /***
197      * property <code>factory.requestHelper</code>.
198      */
199     public static final String PROPERTY_CLASS_REQUESTHELPERFACTORY = "factory.requestHelper"; //$NON-NLS-1$
200 
201     /***
202      * property <code>locale.provider</code>.
203      */
204     public static final String PROPERTY_CLASS_LOCALEPROVIDER = "locale.provider"; //$NON-NLS-1$
205 
206     /***
207      * property <code>locale.resolver</code>.
208      */
209     public static final String PROPERTY_CLASS_LOCALERESOLVER = "locale.resolver"; //$NON-NLS-1$
210 
211     /***
212      * property <code>css.tr.even</code>: holds the name of the css class for even rows. Defaults to
213      * <code>even</code>.
214      */
215     public static final String PROPERTY_CSS_TR_EVEN = "css.tr.even"; //$NON-NLS-1$
216 
217     /***
218      * property <code>css.tr.odd</code>: holds the name of the css class for odd rows. Defaults to <code>odd</code>.
219      */
220     public static final String PROPERTY_CSS_TR_ODD = "css.tr.odd"; //$NON-NLS-1$
221 
222     /***
223      * property <code>css.table</code>: holds the name of the css class added to the main table tag. By default no
224      * css class is added.
225      */
226     public static final String PROPERTY_CSS_TABLE = "css.table"; //$NON-NLS-1$
227 
228     /***
229      * property <code>css.th.sortable</code>: holds the name of the css class added to the the header of a sortable
230      * column. By default no css class is added.
231      */
232     public static final String PROPERTY_CSS_TH_SORTABLE = "css.th.sortable"; //$NON-NLS-1$
233 
234     /***
235      * property <code>css.th.sorted</code>: holds the name of the css class added to the the header of a sorted
236      * column. Defaults to <code>sorted</code>.
237      */
238     public static final String PROPERTY_CSS_TH_SORTED = "css.th.sorted"; //$NON-NLS-1$
239 
240     /***
241      * property <code>css.th.ascending</code>: holds the name of the css class added to the the header of a column
242      * sorted in ascending order. Defaults to <code>order1</code>.
243      */
244     public static final String PROPERTY_CSS_TH_SORTED_ASCENDING = "css.th.ascending"; //$NON-NLS-1$
245 
246     /***
247      * property <code>css.th.descending</code>: holds the name of the css class added to the the header of a column
248      * sorted in descending order. Defaults to <code>order2</code>.
249      */
250     public static final String PROPERTY_CSS_TH_SORTED_DESCENDING = "css.th.descending"; //$NON-NLS-1$
251 
252     /***
253      * prefix used for all the properties related to export ("export"). The full property name is <code>export.</code>
254      * <em>[export type]</em><code>.</code><em>[property name]</em>
255      */
256     public static final String PROPERTY_EXPORT_PREFIX = "export"; //$NON-NLS-1$
257 
258     /***
259      * property <code>export.types</code>: holds the list of export available export types.
260      */
261     public static final String PROPERTY_EXPORTTYPES = "export.types"; //$NON-NLS-1$
262 
263     /***
264      * export property <code>label</code>.
265      */
266     public static final String EXPORTPROPERTY_STRING_LABEL = "label"; //$NON-NLS-1$
267 
268     /***
269      * export property <code>class</code>.
270      */
271     public static final String EXPORTPROPERTY_STRING_CLASS = "class"; //$NON-NLS-1$
272 
273     /***
274      * export property <code>include_header</code>.
275      */
276     public static final String EXPORTPROPERTY_BOOLEAN_EXPORTHEADER = "include_header"; //$NON-NLS-1$
277 
278     /***
279      * export property <code>filename</code>.
280      */
281     public static final String EXPORTPROPERTY_STRING_FILENAME = "filename"; //$NON-NLS-1$
282 
283     /***
284      * Separator char used in property names.
285      */
286     private static final char SEP = '.';
287 
288     /***
289      * logger.
290      */
291     private static Log log = LogFactory.getLog(TableProperties.class);
292 
293     /***
294      * The userProperties are local, non-default properties; these settings override the defaults from
295      * displaytag.properties and TableTag.properties.
296      */
297     private static Properties userProperties = new Properties();
298 
299     /***
300      * Configured resource provider. If no ResourceProvider is configured, an no-op one is used. This instance is
301      * initialized at first use and shared.
302      */
303     private static I18nResourceProvider resourceProvider;
304 
305     /***
306      * Configured locale resolver.
307      */
308     private static LocaleResolver localeResolver;
309 
310     /***
311      * TableProperties for each locale are loaded as needed, and cloned for public usage.
312      */
313     private static Map prototypes = new HashMap();
314 
315     /***
316      * Loaded properties (defaults from defaultProperties + custom from bundle).
317      */
318     private Properties properties;
319 
320     /***
321      * The locale for these properties.
322      */
323     private Locale locale;
324 
325     /***
326      * Setter for I18nResourceProvider. A resource provider is usually set using displaytag properties, this accessor is
327      * needed for tests.
328      * @param provider I18nResourceProvider instance
329      */
330     protected static void setResourceProvider(I18nResourceProvider provider)
331     {
332         resourceProvider = provider;
333     }
334 
335     /***
336      * Setter for LocaleResolver. A locale resolver is usually set using displaytag properties, this accessor is needed
337      * for tests.
338      * @param resolver LocaleResolver instance
339      */
340     protected static void setLocaleResolver(LocaleResolver resolver)
341     {
342         localeResolver = resolver;
343     }
344 
345     /***
346      * Loads default properties (TableTag.properties).
347      * @return loaded properties
348      * @throws TablePropertiesLoadException if default properties file can't be found
349      */
350     private static Properties loadBuiltInProperties() throws TablePropertiesLoadException
351     {
352         Properties defaultProperties = new Properties();
353 
354         try
355         {
356             defaultProperties.load(TableProperties.class.getResourceAsStream(DEFAULT_FILENAME));
357         }
358         catch (IOException e)
359         {
360             throw new TablePropertiesLoadException(TableProperties.class, DEFAULT_FILENAME, e);
361         }
362 
363         return defaultProperties;
364     }
365 
366     /***
367      * Loads user properties (displaytag.properties) according to the given locale. User properties are not guarantee to
368      * exist, so the method can return <code>null</code> (no exception will be thrown).
369      * @param locale requested Locale
370      * @return loaded properties
371      */
372     private static ResourceBundle loadUserProperties(Locale locale)
373     {
374         ResourceBundle bundle = null;
375         try
376         {
377             bundle = ResourceBundle.getBundle(LOCAL_PROPERTIES, locale);
378         }
379         catch (MissingResourceException e)
380         {
381             // if no resource bundle is found, try using the context classloader
382             try
383             {
384                 bundle = ResourceBundle.getBundle(LOCAL_PROPERTIES, locale, Thread
385                     .currentThread()
386                     .getContextClassLoader());
387             }
388             catch (MissingResourceException mre)
389             {
390                 if (log.isDebugEnabled())
391                 {
392                     log.debug(Messages.getString("TableProperties.propertiesnotfound", //$NON-NLS-1$
393                         new Object[]{mre.getMessage()}));
394                 }
395             }
396         }
397 
398         return bundle;
399     }
400 
401     /***
402      * Returns the configured Locale Resolver. This method is called before the loading of localized properties.
403      * @return LocaleResolver instance.
404      * @throws TablePropertiesLoadException if the default <code>TableTag.properties</code> file is not found.
405      */
406     public static LocaleResolver getLocaleResolverInstance() throws TablePropertiesLoadException
407     {
408         // special handling, table properties is not yet instantiated
409         String className = null;
410 
411         ResourceBundle defaultUserProperties = loadUserProperties(Locale.getDefault());
412 
413         // if available, user properties have higher precedence
414         if (defaultUserProperties != null)
415         {
416             try
417             {
418                 className = defaultUserProperties.getString(PROPERTY_CLASS_LOCALERESOLVER);
419             }
420             catch (MissingResourceException e)
421             {
422                 // no problem
423             }
424         }
425 
426         // still null? load defaults
427         if (className == null)
428         {
429             Properties defaults = loadBuiltInProperties();
430             className = defaults.getProperty(PROPERTY_CLASS_LOCALERESOLVER);
431         }
432 
433         if (localeResolver == null)
434         {
435             if (className != null)
436             {
437                 try
438                 {
439                     Class classProperty = ReflectHelper.classForName(className);
440                     localeResolver = (LocaleResolver) classProperty.newInstance();
441 
442                     log.info(Messages.getString("TableProperties.classinitializedto", //$NON-NLS-1$
443                         new Object[]{ClassUtils.getShortClassName(LocaleResolver.class), className}));
444                 }
445                 catch (Throwable e)
446                 {
447                     log.warn(Messages.getString("TableProperties.errorloading", //$NON-NLS-1$
448                         new Object[]{
449                             ClassUtils.getShortClassName(LocaleResolver.class),
450                             e.getClass().getName(),
451                             e.getMessage()}));
452                 }
453             }
454             else
455             {
456                 log.info(Messages.getString("TableProperties.noconfigured", //$NON-NLS-1$
457                     new Object[]{ClassUtils.getShortClassName(LocaleResolver.class)}));
458             }
459 
460             // still null?
461             if (localeResolver == null)
462             {
463                 // fallback locale resolver
464                 localeResolver = new LocaleResolver()
465                 {
466 
467                     public Locale resolveLocale(HttpServletRequest request)
468                     {
469                         return request.getLocale();
470                     }
471                 };
472             }
473         }
474 
475         return localeResolver;
476     }
477 
478     /***
479      * Initialize a new TableProperties loading the default properties file and the user defined one. There is no
480      * caching used here, caching is assumed to occur in the getInstance factory method.
481      * @param myLocale the locale we are in
482      * @throws TablePropertiesLoadException for errors during loading of properties files
483      */
484     private TableProperties(Locale myLocale) throws TablePropertiesLoadException
485     {
486         this.locale = myLocale;
487         // default properties will not change unless this class is reloaded
488         Properties defaultProperties = loadBuiltInProperties();
489 
490         properties = new Properties(defaultProperties);
491         addProperties(myLocale);
492 
493         // Now copy in the user properties (properties file set by calling setUserProperties()).
494         // note setUserProperties() MUST BE CALLED before the first TableProperties instantation
495         Enumeration keys = userProperties.keys();
496         while (keys.hasMoreElements())
497         {
498             String key = (String) keys.nextElement();
499             if (key != null)
500             {
501                 properties.setProperty(key, (String) userProperties.get(key));
502             }
503         }
504     }
505 
506     /***
507      * Try to load the properties from the local properties file, displaytag.properties, and merge them into the
508      * existing properties.
509      * @param userLocale the locale from which the properties are to be loaded
510      */
511     private void addProperties(Locale userLocale)
512     {
513         ResourceBundle bundle = loadUserProperties(userLocale);
514 
515         if (bundle != null)
516         {
517             Enumeration keys = bundle.getKeys();
518             while (keys.hasMoreElements())
519             {
520                 String key = (String) keys.nextElement();
521                 properties.setProperty(key, bundle.getString(key));
522             }
523         }
524     }
525 
526     /***
527      * Clones the properties as well.
528      * @return a new clone of oneself
529      */
530     protected Object clone()
531     {
532         TableProperties twin;
533         try
534         {
535             twin = (TableProperties) super.clone();
536         }
537         catch (CloneNotSupportedException e)
538         {
539             // should never happen
540             throw new WrappedRuntimeException(getClass(), e);
541         }
542         twin.properties = (Properties) this.properties.clone();
543         return twin;
544     }
545 
546     /***
547      * Returns a new TableProperties instance for the given locale.
548      * @param request HttpServletRequest needed to extract the locale to use. If null the default locale will be used.
549      * @return TableProperties instance
550      */
551     public static TableProperties getInstance(HttpServletRequest request)
552     {
553         Locale locale;
554         if (request != null)
555         {
556             locale = getLocaleResolverInstance().resolveLocale(request);
557         }
558         else
559         {
560             // for some configuration parameters locale doesn't matter
561             locale = Locale.getDefault();
562         }
563 
564         TableProperties props = (TableProperties) prototypes.get(locale);
565         if (props == null)
566         {
567             TableProperties lprops = new TableProperties(locale);
568             prototypes.put(locale, lprops);
569             props = lprops;
570         }
571         return (TableProperties) props.clone();
572     }
573 
574     /***
575      * Unload all cached properties. This will not clear properties set by by setUserProperties; you must clear those
576      * manually.
577      */
578     public static void clearProperties()
579     {
580         prototypes.clear();
581     }
582 
583     /***
584      * Local, non-default properties; these settings override the defaults from displaytag.properties and
585      * TableTag.properties. Please note that the values are copied in, so that multiple calls with non-overlapping
586      * properties will be merged, not overwritten. Note: setUserProperties() MUST BE CALLED before the first
587      * TableProperties instantation.
588      * @param overrideProperties - The local, non-default properties
589      */
590     public static void setUserProperties(Properties overrideProperties)
591     {
592         // copy keys here, so that this can be invoked more than once from different sources.
593         // if default properties are not yet loaded they will be copied in constructor
594         Enumeration keys = overrideProperties.keys();
595         while (keys.hasMoreElements())
596         {
597             String key = (String) keys.nextElement();
598             if (key != null)
599             {
600                 userProperties.setProperty(key, (String) overrideProperties.get(key));
601             }
602         }
603     }
604 
605     /***
606      * The locale for which these properties are intended.
607      * @return the locale
608      */
609     public Locale getLocale()
610     {
611         return locale;
612     }
613 
614     /***
615      * Getter for the <code>PROPERTY_STRING_PAGING_INVALIDPAGE</code> property.
616      * @return String
617      */
618     public String getPagingInvalidPage()
619     {
620         return getProperty(PROPERTY_STRING_PAGING_INVALIDPAGE);
621     }
622 
623     /***
624      * Getter for the <code>PROPERTY_STRING_PAGING_ITEM_NAME</code> property.
625      * @return String
626      */
627     public String getPagingItemName()
628     {
629         return getProperty(PROPERTY_STRING_PAGING_ITEM_NAME);
630     }
631 
632     /***
633      * Getter for the <code>PROPERTY_STRING_PAGING_ITEMS_NAME</code> property.
634      * @return String
635      */
636     public String getPagingItemsName()
637     {
638         return getProperty(PROPERTY_STRING_PAGING_ITEMS_NAME);
639     }
640 
641     /***
642      * Getter for the <code>PROPERTY_STRING_PAGING_NOITEMS</code> property.
643      * @return String
644      */
645     public String getPagingFoundNoItems()
646     {
647         return getProperty(PROPERTY_STRING_PAGING_NOITEMS);
648     }
649 
650     /***
651      * Getter for the <code>PROPERTY_STRING_PAGING_FOUND_ONEITEM</code> property.
652      * @return String
653      */
654     public String getPagingFoundOneItem()
655     {
656         return getProperty(PROPERTY_STRING_PAGING_FOUND_ONEITEM);
657     }
658 
659     /***
660      * Getter for the <code>PROPERTY_STRING_PAGING_FOUND_ALLITEMS</code> property.
661      * @return String
662      */
663     public String getPagingFoundAllItems()
664     {
665         return getProperty(PROPERTY_STRING_PAGING_FOUND_ALLITEMS);
666     }
667 
668     /***
669      * Getter for the <code>PROPERTY_STRING_PAGING_FOUND_SOMEITEMS</code> property.
670      * @return String
671      */
672     public String getPagingFoundSomeItems()
673     {
674         return getProperty(PROPERTY_STRING_PAGING_FOUND_SOMEITEMS);
675     }
676 
677     /***
678      * Getter for the <code>PROPERTY_INT_PAGING_GROUPSIZE</code> property.
679      * @return int
680      */
681     public int getPagingGroupSize()
682     {
683         // default size is 8
684         return getIntProperty(PROPERTY_INT_PAGING_GROUPSIZE, 8);
685     }
686 
687     /***
688      * Getter for the <code>PROPERTY_STRING_PAGING_BANNER_ONEPAGE</code> property.
689      * @return String
690      */
691     public String getPagingBannerOnePage()
692     {
693         return getProperty(PROPERTY_STRING_PAGING_BANNER_ONEPAGE);
694     }
695 
696     /***
697      * Getter for the <code>PROPERTY_STRING_PAGING_BANNER_FIRST</code> property.
698      * @return String
699      */
700     public String getPagingBannerFirst()
701     {
702         return getProperty(PROPERTY_STRING_PAGING_BANNER_FIRST);
703     }
704 
705     /***
706      * Getter for the <code>PROPERTY_STRING_PAGING_BANNER_LAST</code> property.
707      * @return String
708      */
709     public String getPagingBannerLast()
710     {
711         return getProperty(PROPERTY_STRING_PAGING_BANNER_LAST);
712     }
713 
714     /***
715      * Getter for the <code>PROPERTY_STRING_PAGING_BANNER_FULL</code> property.
716      * @return String
717      */
718     public String getPagingBannerFull()
719     {
720         return getProperty(PROPERTY_STRING_PAGING_BANNER_FULL);
721     }
722 
723     /***
724      * Getter for the <code>PROPERTY_STRING_PAGING_PAGE_LINK</code> property.
725      * @return String
726      */
727     public String getPagingPageLink()
728     {
729         return getProperty(PROPERTY_STRING_PAGING_PAGE_LINK);
730     }
731 
732     /***
733      * Getter for the <code>PROPERTY_STRING_PAGING_PAGE_SELECTED</code> property.
734      * @return String
735      */
736     public String getPagingPageSelected()
737     {
738         return getProperty(PROPERTY_STRING_PAGING_PAGE_SELECTED);
739     }
740 
741     /***
742      * Getter for the <code>PROPERTY_STRING_PAGING_PAGE_SPARATOR</code> property.
743      * @return String
744      */
745     public String getPagingPageSeparator()
746     {
747         return getProperty(PROPERTY_STRING_PAGING_PAGE_SPARATOR);
748     }
749 
750     /***
751      * Is the given export option enabled?
752      * @param exportType instance of MediaTypeEnum
753      * @return boolean true if export is enabled
754      */
755     public boolean getAddExport(MediaTypeEnum exportType)
756     {
757         return getBooleanProperty(PROPERTY_EXPORT_PREFIX + SEP + exportType.getName());
758     }
759 
760     /***
761      * Should headers be included in given export type?
762      * @param exportType instance of MediaTypeEnum
763      * @return boolean true if export should include headers
764      */
765     public boolean getExportHeader(MediaTypeEnum exportType)
766     {
767         return getBooleanProperty(PROPERTY_EXPORT_PREFIX
768             + SEP
769             + exportType.getName()
770             + SEP
771             + EXPORTPROPERTY_BOOLEAN_EXPORTHEADER);
772     }
773 
774     /***
775      * Returns the label for the given export option.
776      * @param exportType instance of MediaTypeEnum
777      * @return String label
778      */
779     public String getExportLabel(MediaTypeEnum exportType)
780     {
781         return getProperty(PROPERTY_EXPORT_PREFIX + SEP + exportType.getName() + SEP + EXPORTPROPERTY_STRING_LABEL);
782     }
783 
784     /***
785      * Returns the file name for the given media. Can be null
786      * @param exportType instance of MediaTypeEnum
787      * @return String filename
788      */
789     public String getExportFileName(MediaTypeEnum exportType)
790     {
791         return getProperty(PROPERTY_EXPORT_PREFIX + SEP + exportType.getName() + SEP + EXPORTPROPERTY_STRING_FILENAME);
792     }
793 
794     /***
795      * Getter for the <code>PROPERTY_BOOLEAN_EXPORTDECORATED</code> property.
796      * @return boolean <code>true</code> if decorators should be used in exporting
797      */
798     public boolean getExportDecorated()
799     {
800         return getBooleanProperty(PROPERTY_BOOLEAN_EXPORTDECORATED);
801     }
802 
803     /***
804      * Getter for the <code>PROPERTY_STRING_EXPORTBANNER</code> property.
805      * @return String
806      */
807     public String getExportBanner()
808     {
809         return getProperty(PROPERTY_STRING_EXPORTBANNER);
810     }
811 
812     /***
813      * Getter for the <code>PROPERTY_STRING_EXPORTBANNER_SEPARATOR</code> property.
814      * @return String
815      */
816     public String getExportBannerSeparator()
817     {
818         return getProperty(PROPERTY_STRING_EXPORTBANNER_SEPARATOR);
819     }
820 
821     /***
822      * Getter for the <code>PROPERTY_BOOLEAN_SHOWHEADER</code> property.
823      * @return boolean
824      */
825     public boolean getShowHeader()
826     {
827         return getBooleanProperty(PROPERTY_BOOLEAN_SHOWHEADER);
828     }
829 
830     /***
831      * Getter for the <code>PROPERTY_STRING_EMPTYLIST_MESSAGE</code> property.
832      * @return String
833      */
834     public String getEmptyListMessage()
835     {
836         return getProperty(PROPERTY_STRING_EMPTYLIST_MESSAGE);
837     }
838 
839     /***
840      * Getter for the <code>PROPERTY_STRING_EMPTYLISTROW_MESSAGE</code> property.
841      * @return String
842      */
843     public String getEmptyListRowMessage()
844     {
845         return getProperty(PROPERTY_STRING_EMPTYLISTROW_MESSAGE);
846     }
847 
848     /***
849      * Getter for the <code>PROPERTY_BOOLEAN_EMPTYLIST_SHOWTABLE</code> property.
850      * @return boolean <code>true</code> if table should be displayed also if no items are found
851      */
852     public boolean getEmptyListShowTable()
853     {
854         return getBooleanProperty(PROPERTY_BOOLEAN_EMPTYLIST_SHOWTABLE);
855     }
856 
857     /***
858      * Getter for the <code>PROPERTY_STRING_EXPORTAMOUNT</code> property.
859      * @return boolean <code>true</code> if <code>export.amount</code> is <code>list</code>
860      */
861     public boolean getExportFullList()
862     {
863         return "list".equals(getProperty(PROPERTY_STRING_EXPORTAMOUNT)); //$NON-NLS-1$
864     }
865 
866     /***
867      * Getter for the <code>PROPERTY_STRING_SORTAMOUNT</code> property.
868      * @return boolean <code>true</code> if <code>sort.amount</code> is <code>list</code>
869      */
870     public boolean getSortFullList()
871     {
872         return "list".equals(getProperty(PROPERTY_STRING_SORTAMOUNT)); //$NON-NLS-1$
873     }
874 
875     /***
876      * Should paging banner be added before the table?
877      * @return boolean
878      */
879     public boolean getAddPagingBannerTop()
880     {
881         String placement = getProperty(PROPERTY_STRING_BANNER_PLACEMENT);
882         return "top".equals(placement) || "both".equals(placement); //$NON-NLS-1$ //$NON-NLS-2$
883     }
884 
885     /***
886      * Should paging banner be added after the table?
887      * @return boolean
888      */
889     public boolean getAddPagingBannerBottom()
890     {
891         String placement = getProperty(PROPERTY_STRING_BANNER_PLACEMENT);
892         return "bottom".equals(placement) || "both".equals(placement); //$NON-NLS-1$ //$NON-NLS-2$
893     }
894 
895     /***
896      * Returns the appropriate css class for a table row.
897      * @param rowNumber row number
898      * @return the value of <code>PROPERTY_CSS_TR_EVEN</code> if rowNumber is even or <code>PROPERTY_CSS_TR_ODD</code>
899      * if rowNumber is odd.
900      */
901     public String getCssRow(int rowNumber)
902     {
903         return getProperty((rowNumber % 2 == 0) ? PROPERTY_CSS_TR_ODD : PROPERTY_CSS_TR_EVEN);
904     }
905 
906     /***
907      * Returns the appropriate css class for a sorted column header.
908      * @param ascending <code>true</code> if column is sorded in ascending order.
909      * @return the value of <code>PROPERTY_CSS_TH_SORTED_ASCENDING</code> if column is sorded in ascending order or
910      * <code>PROPERTY_CSS_TH_SORTED_DESCENDING</code> if column is sorded in descending order.
911      */
912     public String getCssOrder(boolean ascending)
913     {
914         return getProperty(ascending ? PROPERTY_CSS_TH_SORTED_ASCENDING : PROPERTY_CSS_TH_SORTED_DESCENDING);
915     }
916 
917     /***
918      * Returns the configured css class for a sorted column header.
919      * @return the value of <code>PROPERTY_CSS_TH_SORTED</code>
920      */
921     public String getCssSorted()
922     {
923         return getProperty(PROPERTY_CSS_TH_SORTED);
924     }
925 
926     /***
927      * Returns the configured css class for the main table tag.
928      * @return the value of <code>PROPERTY_CSS_TABLE</code>
929      */
930     public String getCssTable()
931     {
932         return getProperty(PROPERTY_CSS_TABLE);
933     }
934 
935     /***
936      * Returns the configured css class for a sortable column header.
937      * @return the value of <code>PROPERTY_CSS_TH_SORTABLE</code>
938      */
939     public String getCssSortable()
940     {
941         return getProperty(PROPERTY_CSS_TH_SORTABLE);
942     }
943 
944     /***
945      * Returns the configured list of media.
946      * @return the value of <code>PROPERTY_EXPORTTYPES</code>
947      */
948     public String[] getExportTypes()
949     {
950         String list = getProperty(PROPERTY_EXPORTTYPES);
951         if (list == null)
952         {
953             return new String[0];
954         }
955 
956         return StringUtils.split(list);
957     }
958 
959     /***
960      * Returns the class responsible for the given export.
961      * @param exportName export name
962      * @return String classname
963      */
964     public String getExportClass(String exportName)
965     {
966         return getProperty(PROPERTY_EXPORT_PREFIX + SEP + exportName + SEP + EXPORTPROPERTY_STRING_CLASS);
967     }
968 
969     /***
970      * Returns an instance of configured requestHelperFactory.
971      * @return RequestHelperFactory instance.
972      * @throws FactoryInstantiationException if unable to load or instantiate the configurated class.
973      */
974     public RequestHelperFactory getRequestHelperFactoryInstance() throws FactoryInstantiationException
975     {
976         Object loadedObject = getClassPropertyInstance(PROPERTY_CLASS_REQUESTHELPERFACTORY);
977 
978         // should not be null, but avoid errors just in case... see DISPL-148
979         if (loadedObject == null)
980         {
981             return new DefaultRequestHelperFactory();
982         }
983 
984         try
985         {
986             return (RequestHelperFactory) loadedObject;
987         }
988         catch (ClassCastException e)
989         {
990             throw new FactoryInstantiationException(getClass(), PROPERTY_CLASS_REQUESTHELPERFACTORY, loadedObject
991                 .getClass()
992                 .getName(), e);
993         }
994     }
995 
996     /***
997      * Returns the configured resource provider instance. If necessary instantiate the resource provider from config and
998      * then keep a cached instance.
999      * @return I18nResourceProvider instance.
1000      * @see I18nResourceProvider
1001      */
1002     public I18nResourceProvider geResourceProvider()
1003     {
1004         String className = getProperty(PROPERTY_CLASS_LOCALEPROVIDER);
1005 
1006         if (resourceProvider == null)
1007         {
1008             if (className != null)
1009             {
1010                 try
1011                 {
1012                     Class classProperty = ReflectHelper.classForName(className);
1013                     resourceProvider = (I18nResourceProvider) classProperty.newInstance();
1014 
1015                     log.info(Messages.getString("TableProperties.classinitializedto", //$NON-NLS-1$
1016                         new Object[]{ClassUtils.getShortClassName(I18nResourceProvider.class), className}));
1017                 }
1018                 catch (Throwable e)
1019                 {
1020                     log.warn(Messages.getString("TableProperties.errorloading", //$NON-NLS-1$
1021                         new Object[]{
1022                             ClassUtils.getShortClassName(I18nResourceProvider.class),
1023                             e.getClass().getName(),
1024                             e.getMessage()}));
1025                 }
1026             }
1027             else
1028             {
1029                 log.info(Messages.getString("TableProperties.noconfigured", //$NON-NLS-1$
1030                     new Object[]{ClassUtils.getShortClassName(I18nResourceProvider.class)}));
1031             }
1032 
1033             // still null?
1034             if (resourceProvider == null)
1035             {
1036                 // fallback provider, no i18n
1037                 resourceProvider = new I18nResourceProvider()
1038                 {
1039 
1040                     // Always returns null
1041                     public String getResource(String titleKey, String property, Tag tag, PageContext context)
1042                     {
1043                         return null;
1044                     }
1045                 };
1046             }
1047         }
1048 
1049         return resourceProvider;
1050     }
1051 
1052     /***
1053      * Reads a String property.
1054      * @param key property name
1055      * @return property value or <code>null</code> if property is not found
1056      */
1057     private String getProperty(String key)
1058     {
1059         return this.properties.getProperty(key);
1060     }
1061 
1062     /***
1063      * Sets a property.
1064      * @param key property name
1065      * @param value property value
1066      */
1067     public void setProperty(String key, String value)
1068     {
1069         this.properties.setProperty(key, value);
1070     }
1071 
1072     /***
1073      * Reads a boolean property.
1074      * @param key property name
1075      * @return boolean <code>true</code> if the property value is "true", <code>false</code> for any other value.
1076      */
1077     private boolean getBooleanProperty(String key)
1078     {
1079         return Boolean.TRUE.toString().equals(getProperty(key));
1080     }
1081 
1082     /***
1083      * Returns an instance of a configured Class. Returns a configured Class instantiated
1084      * callingClass.forName([configuration value]).
1085      * @param key configuration key
1086      * @return instance of configured class
1087      * @throws FactoryInstantiationException if unable to load or instantiate the configurated class.
1088      */
1089     private Object getClassPropertyInstance(String key) throws FactoryInstantiationException
1090     {
1091         String className = getProperty(key);
1092 
1093         // shouldn't be null, but better check it
1094         if (className == null)
1095         {
1096             return null;
1097         }
1098 
1099         try
1100         {
1101             Class classProperty = ReflectHelper.classForName(className);
1102             return classProperty.newInstance();
1103         }
1104         catch (Exception e)
1105         {
1106             throw new FactoryInstantiationException(getClass(), key, className, e);
1107         }
1108     }
1109 
1110     /***
1111      * Reads an int property.
1112      * @param key property name
1113      * @param defaultValue default value returned if property is not found or not a valid int value
1114      * @return property value
1115      */
1116     private int getIntProperty(String key, int defaultValue)
1117     {
1118         try
1119         {
1120             return Integer.parseInt(getProperty(key));
1121         }
1122         catch (NumberFormatException e)
1123         {
1124             // Don't care, use default
1125             log.warn(Messages.getString("TableProperties.invalidvalue", //$NON-NLS-1$
1126                 new Object[]{key, getProperty(key), new Integer(defaultValue)}));
1127         }
1128 
1129         return defaultValue;
1130     }
1131 }