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