/
HTMLUtils.java
923 lines (855 loc) · 41.4 KB
/
HTMLUtils.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
/*
* #%L
* Netarchivesuite - common
* %%
* Copyright (C) 2005 - 2017 The Royal Danish Library,
* the National Library of France and the Austrian National Library.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 2.1 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-2.1.html>.
* #L%
*/
package dk.netarkivet.common.webinterface;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dk.netarkivet.common.CommonSettings;
import dk.netarkivet.common.Constants;
import dk.netarkivet.common.exceptions.ArgumentNotValid;
import dk.netarkivet.common.exceptions.ForwardedToErrorPage;
import dk.netarkivet.common.exceptions.IOFailure;
import dk.netarkivet.common.utils.I18n;
import dk.netarkivet.common.utils.Settings;
import dk.netarkivet.common.utils.StringTree;
/**
* This is a utility class containing methods for use in the GUI for netarkivet.
*/
public class HTMLUtils {
/** Date format for FMT timestamps */
private static final String DATE_FMT_STRING = "yyyy/MM/dd HH:mm:ss";
/** Web page title placeholder. */
private static String TITLE_PLACEHOLDER = "STRING_1";
/** External JavaScript files placeholder. */
private static String JS_PLACEHOLDER = "JS_TO_INCLUDE";
private static String WEBPAGE_HEADER_TEMPLATE_TOP = "<!DOCTYPE html "
+ "PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \n "
+ " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\"" + " xml:lang=\"en\" lang=\"en\">\n" + "<head>\n"
+ "<meta content=\"text/html; charset=UTF-8\" " + "http-equiv= \"content-type\" />"
+ "<meta http-equiv=\"Expires\" content=\"0\"/>\n"
+ "<meta http-equiv=\"Cache-Control\" content=\"no-cache\"/>\n"
+ "<meta http-equiv=\"Pragma\" content=\"no-cache\"/> \n";
private static String WEBPAGE_HEADER_AUTOREFRESH = "<meta http-equiv=\"refresh\" content=\"" + TITLE_PLACEHOLDER
+ "\"/> \n";
private static String WEBPAGE_HEADER_TEMPLATE_BOTTOM = "<title>" + TITLE_PLACEHOLDER + "</title>\n"
+ "<script type=\"text/javascript\">\n" + "<!--\n" + "function giveFocus() {\n"
+ " var e = document.getElementById('focusElement');\n" + " if (e != null) {\n"
+ " var elms = e.getElementsByTagName('*');\n" + " if (elms != null && elms.length != null "
+ " && elms.item != null && elms.length > 0) {\n" + " var e2 = elms.item(0);\n"
+ " if (e2 != null && e2.focus != null) {\n" + " }\n"
+ " e2.focus();\n" + " }\n" + " }\n" + "}\n" + "-->\n" + "</script>\n"
+ JS_PLACEHOLDER + "\n" + "<link rel=\"stylesheet\" href=\"./netarkivet.css\" " + "type=\"text/css\" />\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" "
+ "href=\"./jscalendar/calendar-win2k-cold-1.css\" " + "title=\"./jscalendar/win2k-cold-1\" />\n"
+ "</head> <body onload=\"giveFocus()\">\n";
/** Logger for this class. */
private static final Logger log = LoggerFactory.getLogger(HTMLUtils.class);
/** Translations for this module. */
private static final I18n I18N = new I18n(Constants.TRANSLATIONS_BUNDLE);
/**
* Private constructor. There is no reason to instantiate this class.
*/
private HTMLUtils() {
// Nothing to initialize
}
/**
* Url encodes a string in UTF-8. This encodes _all_ non-letter non-number characters except '-', '_' and '.'. The
* characters '/' and ':' are encoded.
*
* @param s the string to encode
* @return the encoded string
*/
public static String encode(String s) {
ArgumentNotValid.checkNotNull(s, "s");
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ArgumentNotValid(URLEncoder.class.getName() + " does not support UTF-8", e);
}
}
/**
* Url decodes a string encoded in UTF-8.
*
* @param s the string to decode
* @return the decoded string
*/
public static String decode(String s) {
ArgumentNotValid.checkNotNull(s, "s");
try {
return URLDecoder.decode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ArgumentNotValid(URLDecoder.class.getName() + " does not support UTF-8", e);
}
}
/**
* Prints the header information for the webpages in the GUI. This includes the navigation menu, and links for
* changing the language. The title of the page is generated internationalised from sitesections. If you want to
* specify it, use the overloaded method.
*
* @param context The context of the web page request.
* @throws IOException if an error occurs during writing of output.
*/
public static void generateHeader(PageContext context) throws IOException {
ArgumentNotValid.checkNotNull(context, "context");
String url = ((HttpServletRequest) context.getRequest()).getRequestURL().toString();
Locale locale = context.getResponse().getLocale();
String title = getTitle(url, locale);
generateHeader(title, context);
}
/**
* Prints the header information for the webpages in the GUI. This includes the navigation menu, and links for
* changing the language. The title of the page is generated internationalised from sitesections. If you want to
* specify it, use the overloaded method.
*
* @param context The context of the web page request.
* @throws IOException if an error occurs during writing of output.
*/
public static void generateHeader(PageContext context, String... jsToInclude) throws IOException {
ArgumentNotValid.checkNotNull(context, "context");
String url = ((HttpServletRequest) context.getRequest()).getRequestURL().toString();
Locale locale = context.getResponse().getLocale();
String title = getTitle(url, locale);
generateHeader(title, context, jsToInclude);
}
/**
* Prints the header information for the webpages in the GUI. This includes the navigation menu, and links for
* changing the language. The title of the page is generated internationalised from sitesections. If you want to
* specify it, use the overloaded method.
*
* @param context The context of the web page request.
* @param refreshInSeconds auto-refresh time in seconds
* @throws IOException if an error occurs during writing of output.
*/
public static void generateHeader(PageContext context, long refreshInSeconds) throws IOException {
ArgumentNotValid.checkNotNull(context, "context");
String url = ((HttpServletRequest) context.getRequest()).getRequestURL().toString();
Locale locale = context.getResponse().getLocale();
String title = getTitle(url, locale);
generateHeader(title, refreshInSeconds, context);
}
/**
* Prints the header information for the webpages in the GUI. This includes the navigation menu, and links for
* changing the language.
*
* @param title An internationalised title of the page.
* @param context The context of the web page request.
* @param jsToInclude path(s) to external .js files to include in header.
* @throws IOException if an error occurs during writing to output.
*/
public static void generateHeader(String title, PageContext context, String... jsToInclude) throws IOException {
ArgumentNotValid.checkNotNull(title, "title");
ArgumentNotValid.checkNotNull(context, "context");
JspWriter out = context.getOut();
String url = ((HttpServletRequest) context.getRequest()).getRequestURL().toString();
Locale locale = context.getResponse().getLocale();
title = escapeHtmlValues(title);
log.debug("Loading URL '" + url + "' with title '" + title + "'");
out.print(WEBPAGE_HEADER_TEMPLATE_TOP);
String includeJs = "";
if (jsToInclude != null && jsToInclude.length > 0) {
for (String js : jsToInclude) {
includeJs += "<script type=\"text/javascript\" src=\"" + js + "\"></script>\n";
}
}
out.print(WEBPAGE_HEADER_TEMPLATE_BOTTOM.replace(TITLE_PLACEHOLDER, title).replace(JS_PLACEHOLDER, includeJs));
// Start the two column / one row table which fills the page
out.print("<table id =\"main_table\"><tr>\n");
// fill in data in the left column
StringBuilder sb = new StringBuilder();
generateNavigationTree(sb, url, locale);
out.print(sb.toString());
// The right column contains the active form content for this page
out.print("<td valign = \"top\" >\n");
// Language links
generateLanguageLinks(out);
log.debug("Loaded URL '" + url + "' with title '" + title + "'");
}
/**
* Prints the header information for the webpages in the GUI. This includes the navigation menu, and links for
* changing the language.
*
* @param title An internationalised title of the page.
* @param context The context of the web page request.
* @param refreshInSeconds auto-refresh time in seconds
* @throws IOException if an error occurs during writing to output.
*/
public static void generateHeader(String title, long refreshInSeconds, PageContext context) throws IOException {
ArgumentNotValid.checkNotNull(title, "title");
ArgumentNotValid.checkNotNull(context, "context");
JspWriter out = context.getOut();
String url = ((HttpServletRequest) context.getRequest()).getRequestURL().toString();
Locale locale = context.getResponse().getLocale();
title = escapeHtmlValues(title);
log.debug("Loading URL '" + url + "' with title '" + title + "'");
out.print(WEBPAGE_HEADER_TEMPLATE_TOP);
if (refreshInSeconds > 0) {
out.print(WEBPAGE_HEADER_AUTOREFRESH.replace(TITLE_PLACEHOLDER, Long.toString(refreshInSeconds)));
}
out.print(WEBPAGE_HEADER_TEMPLATE_BOTTOM.replace(TITLE_PLACEHOLDER, title).replace(JS_PLACEHOLDER, ""));
// Start the two column / one row table which fills the page
out.print("<table id =\"main_table\"><tr>\n");
// fill in data in the left column
StringBuilder sb = new StringBuilder();
generateNavigationTree(sb, url, locale);
out.print(sb.toString());
// The right column contains the active form content for this page
out.print("<td valign = \"top\" >\n");
// Language links
generateLanguageLinks(out);
log.debug("Loaded URL '" + url + "' with title '" + title + "'");
}
/**
* Get the locale according to header context information.
*
* @param context The context of the web page request.
* @return The locale given in the the page response.
*/
public static Locale getLocaleObject(PageContext context) {
ArgumentNotValid.checkNotNull(context, "context");
return context.getResponse().getLocale();
}
/**
* Prints out links to change languages. Will read locales and names of languages from settings, and write them as
* links to the page "lang.jsp". The locale will be given to this page as a parameter, the name will be shown as the
* text of the link
*
* @param out the writer to which the links are written.
* @throws IOException if an error occurs during writing of output.
*/
private static void generateLanguageLinks(JspWriter out) throws IOException {
out.print("<div class=\"languagelinks\">");
StringTree<String> webinterfaceSettings = Settings.getTree(CommonSettings.WEBINTERFACE_SETTINGS);
for (StringTree<String> language : webinterfaceSettings.getSubTrees(CommonSettings.WEBINTERFACE_LANGUAGE)) {
out.print(String.format("<a href=\"lang.jsp?locale=%s&name=%s\">%s</a> ",
escapeHtmlValues(encode(language.getValue(CommonSettings.WEBINTERFACE_LANGUAGE_LOCALE))),
escapeHtmlValues(encode(language.getValue(CommonSettings.WEBINTERFACE_LANGUAGE_NAME))),
escapeHtmlValues(language.getValue(CommonSettings.WEBINTERFACE_LANGUAGE_NAME))));
}
out.print("</div>");
}
/**
* Prints out the navigation tree appearing as a <td>in the left column of the "main_table" table. Subpages are
* shown only for the currently-active main-heading of the sections defined in settings.
*
* @param out the writer to which the output must be written.
* @param url the url of the page.
* @param locale The locale selecting the language.
* @throws IOException if the output cannot be written.
*/
public static void generateNavigationTree(StringBuilder sb, String url, Locale locale) throws IOException {
sb.append("<td valign=\"top\" id=\"menu\">\n");
// The list of menu items is presented as a 1-column table
sb.append("<table id=\"menu_table\">\n");
String s = I18N.getString(locale, "sidebar.title.menu");
sb.append("<tr><td><a class=\"sidebarHeader\" href=\"index.jsp\">"
+ "<img src=\"transparent_menu_logo.png\" alt=\"" + s + "\"/> " + s + "</a></td></tr>\n");
for (SiteSection section : SiteSection.getSections()) {
section.generateNavigationTree(sb, url, locale);
}
sb.append("</table>\n");
sb.append("</td>\n");
}
/**
* Writes out footer information to close the page.
*
* @param out the writer to which the information is written
* @throws IOException if the output cannot be written
*/
public static void generateFooter(JspWriter out) throws IOException {
ArgumentNotValid.checkNotNull(out, "out");
// Close the element containing the page content
out.print("</td>\n");
// Close the single row in the table
out.print("</tr>\n");
// Close the table
out.print("</table>\n");
// Add information about the running system
out.print("<div class='systeminfo'>");
out.print("NetarchiveSuite " + Constants.getVersionString(true) + ", "
+ Settings.get(CommonSettings.ENVIRONMENT_NAME));
out.print("</div>");
// Close the page
out.print("</body></html>");
}
/**
* Create a table element containing the given string, escaping HTML values in the process.
*
* @param s An unescaped string. Any HTML tags in this string will end up escaped away.
* @return The same string escaped and enclosed in td tags.
*/
public static String makeTableElement(String s) {
ArgumentNotValid.checkNotNull(s, "s");
return "<td>" + escapeHtmlValues(s) + "</td>";
}
/**
* Create a table header element containing the given string, escaping HTML values in the process.
*
* @param contents An unescaped string. Any HTML tags in this string will end up escaped away.
* @return The same string escaped and enclosed in th tags.
*/
public static String makeTableHeader(String contents) {
ArgumentNotValid.checkNotNull(contents, "contents");
return "<th>" + escapeHtmlValues(contents) + "</th>";
}
/**
* Create a table row. Note that in contrast to createTableElement and createTableHeader, the contents are not
* escaped. They are expected to contain table elements.
*
* @param contents The contents to put into the table row. The entries will be delimited by newline characters.
* @return The same string escaped and enclosed in td tags.
*/
public static String makeTableRow(String... contents) {
ArgumentNotValid.checkNotNull(contents, "contents");
StringBuilder sb = new StringBuilder("<tr>");
for (String element : contents) {
sb.append(element);
sb.append("\n");
}
sb.append("</tr>\n");
return sb.toString();
}
/**
* Get an HTML representation of the date given.
*
* @param d A date
* @return A representation of the date that can be directly inserted into an HTML document, or the empty string if
* d is null.
* @deprecated Please use <fmt:date> from taglib instead.
*/
public static String makeDate(Date d) {
if (d == null) {
return "";
} else {
return escapeHtmlValues(d.toString());
}
}
/**
* Returns the toString() value of an object or a hyphen if the argument is null.
*
* @param o the given object
* @return o.toString() or "-" if o is null
*/
public static String nullToHyphen(Object o) {
if (o == null) {
return "-";
} else {
return o.toString();
}
}
/**
* Escapes HTML special characters ", &, < and > (but not ').
*
* @param input a string
* @return The string with values escaped. If input is null, the empty string is returned.
*/
public static String escapeHtmlValues(String input) {
if (input == null) {
return "";
}
return input.replaceAll("&", "&").replaceAll("\\\"", """).replaceAll("<", "<")
.replaceAll(">", ">");
}
/**
* Encode a string for use in a URL, then escape characters that must be escaped in HTML. This must be used whenever
* unknown strings are used in URLs that are placed in HTML.
*
* @param input A string
* @return The same string, encoded to be safely placed in a URL in HTML.
*/
public static String encodeAndEscapeHTML(String input) {
ArgumentNotValid.checkNotNull(input, "input");
return escapeHtmlValues(encode(input));
}
/**
* Escapes a string for use in javascript. Replaces " with \" and ' with \', so e.g.
* escapeJavascriptQuotes("\"").equals("\\\"") Also, \ and any non-printable character is escaped for use in
* javascript
*
* @param input a string
* @return The string with values escaped. If input is null, the empty string is returned.
*/
public static String escapeJavascriptQuotes(String input) {
if (input == null) {
return "";
}
input = input.replaceAll("\\\\", "\\\\\\\\");
input = input.replaceAll("\\\"", "\\\\\\\"");
input = input.replaceAll("\\\'", "\\\\\\\'");
input = input.replaceAll("\\\u0000", "\\\\u0000");
input = input.replaceAll("\\\u0001", "\\\\u0001");
input = input.replaceAll("\\\u0002", "\\\\u0002");
input = input.replaceAll("\\\u0003", "\\\\u0003");
input = input.replaceAll("\\\u0004", "\\\\u0004");
input = input.replaceAll("\\\u0005", "\\\\u0005");
input = input.replaceAll("\\\u0006", "\\\\u0006");
input = input.replaceAll("\\\u0007", "\\\\u0007");
input = input.replaceAll("\\\b", "\\\\b");
input = input.replaceAll("\\\t", "\\\\t");
input = input.replaceAll("\\\n", "\\\\n");
// Note: \v is an escape for vertical tab that exists
// in javascript but not in java
input = input.replaceAll("\\\u000B", "\\\\v");
input = input.replaceAll("\\\f", "\\\\f");
input = input.replaceAll("\\\r", "\\\\r");
input = input.replaceAll("\\\u000E", "\\\\u000E");
input = input.replaceAll("\\\u000F", "\\\\u000F");
input = input.replaceAll("\\\u0010", "\\\\u0010");
input = input.replaceAll("\\\u0011", "\\\\u0011");
input = input.replaceAll("\\\u0012", "\\\\u0012");
input = input.replaceAll("\\\u0013", "\\\\u0013");
input = input.replaceAll("\\\u0014", "\\\\u0014");
input = input.replaceAll("\\\u0015", "\\\\u0015");
input = input.replaceAll("\\\u0016", "\\\\u0016");
input = input.replaceAll("\\\u0017", "\\\\u0017");
input = input.replaceAll("\\\u0018", "\\\\u0018");
input = input.replaceAll("\\\u0019", "\\\\u0019");
input = input.replaceAll("\\\u001A", "\\\\u001A");
input = input.replaceAll("\\\u001B", "\\\\u001B");
input = input.replaceAll("\\\u001C", "\\\\u001C");
input = input.replaceAll("\\\u001D", "\\\\u001D");
input = input.replaceAll("\\\u001E", "\\\\u001E");
input = input.replaceAll("\\\u001F", "\\\\u001F");
return input;
}
/**
* Sets the character encoding for reading parameters and content from a request in a JSP page.
*
* @param request The servlet request object
*/
public static void setUTF8(HttpServletRequest request) {
ArgumentNotValid.checkNotNull(request, "request");
// Why is this in an if block? Suppose we forward from a page where
// we read file input from the request. Trying to set the character
// encoding again here will throw an exception!
// This is a bit of a hack - we know that _if_ we have set it,
// we have set it to UTF-8, so this way we won't set it twice...
if (request.getCharacterEncoding() == null || !request.getCharacterEncoding().equals("UTF-8")) {
try {
request.setCharacterEncoding("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ArgumentNotValid("Should never happen! UTF-8 not supported", e);
}
}
}
/**
* Given a URL in the sitesection hierarchy, returns the corresponding page title.
*
* @param url a given URL
* @param locale the current locale
* @return the corresponding page title, or string about "(no title)" if no title can be found
* @throws ArgumentNotValid if the given url or locale is null or url is empty.
*/
public static String getTitle(String url, Locale locale) {
ArgumentNotValid.checkNotNull(locale, "Locale locale");
ArgumentNotValid.checkNotNullOrEmpty(url, "String url");
for (SiteSection section : SiteSection.getSections()) {
String title = section.getTitle(url, locale);
if (title != null) {
return title;
}
}
log.warn("Could not find page title for page '" + url + "'");
return I18N.getString(locale, "pagetitle.unknown");
}
/**
* Get the (CSS) class name for a row in a table. The row count should start at 0.
*
* @param rowCount The number of the row
* @return A CSS class name that should be the class of the TR element.
*/
public static String getRowClass(int rowCount) {
if (rowCount % 6 < 3) {
return "row0";
} else {
return "row1";
}
}
/**
* Get a locale from cookie, if present. The default request locale otherwise.
*
* @param request The request to get the locale for.
* @return The cookie locale, if present. The default request locale otherwise.
*/
public static String getLocale(HttpServletRequest request) {
ArgumentNotValid.checkNotNull(request, "request");
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if (c.getName().equals("locale")) {
return c.getValue();
}
}
}
return request.getLocale().toString();
}
/**
* Forward to our standard error message page with an internationalized message. Note that this <em>doesn't</em>
* throw ForwardedToErrorPage, it is the job of whoever calls this to do that if not within a JSP page (a JSP page
* can just return immediately). All text involved will be HTML-escaped.
*
* @param context The context that the error happened in (the JSP-defined pageContext, typically)
* @param I18N The i18n information
* @param label An i18n label for the error. This label should begin with "errormsg;".
* @param args Any extra args for i18n
* @throws IOFailure If the forward fails
*/
public static void forwardWithErrorMessage(PageContext context, I18n I18N, String label, Object... args) {
// Note that we may not want to be to strict here
// as otherwise information could be lost.
ArgumentNotValid.checkNotNull(context, "context");
ArgumentNotValid.checkNotNull(I18N, "I18N");
ArgumentNotValid.checkNotNull(label, "label");
ArgumentNotValid.checkNotNull(args, "args");
String msg = HTMLUtils.escapeHtmlValues(I18N.getString(context.getResponse().getLocale(), label, args));
context.getRequest().setAttribute("message", msg);
RequestDispatcher rd = context.getServletContext().getRequestDispatcher("/message.jsp");
final String errormsg = "Failed to forward on error " + msg;
try {
rd.forward(context.getRequest(), context.getResponse());
} catch (IOException e) {
log.warn(errormsg, e);
throw new IOFailure(errormsg, e);
} catch (ServletException e) {
log.warn(errormsg, e);
throw new IOFailure(errormsg, e);
}
}
/**
* Forward to our standard error message page with an internationalized message. Note that this <em>doesn't</em>
* throw ForwardedToErrorPage, it is the job of whoever calls this to do that if not within a JSP page (a JSP page
* can just return immediately). The text involved must be HTML-escaped before passing to this method.
*
* @param context The context that the error happened in (the JSP-defined pageContext, typically)
* @param i18n The i18n information
* @param label An i18n label for the error. This label should begin with "errormsg;".
* @param args Any extra args for i18n. These must be valid HTML.
* @throws IOFailure If the forward fails.
*/
public static void forwardWithRawErrorMessage(PageContext context, I18n i18n, String label, Object... args) {
// Note that we may not want to be to strict here
// as otherwise information could be lost.
ArgumentNotValid.checkNotNull(context, "context");
ArgumentNotValid.checkNotNull(I18N, "I18N");
ArgumentNotValid.checkNotNull(label, "label");
ArgumentNotValid.checkNotNull(args, "args");
String msg = i18n.getString(context.getResponse().getLocale(), label, args);
context.getRequest().setAttribute("message", msg);
RequestDispatcher rd = context.getServletContext().getRequestDispatcher("/message.jsp");
try {
rd.forward(context.getRequest(), context.getResponse());
} catch (IOException e) {
final String errormsg = "Failed to forward on error " + msg;
log.warn(errormsg, e);
throw new IOFailure(errormsg, e);
} catch (ServletException e) {
final String errormsg = "Failed to forward on error " + msg;
log.warn(errormsg, e);
throw new IOFailure(errormsg, e);
}
}
/**
* Forward to our standard error message page with an internationalized message, in case of exception. Note that
* this <em>doesn't</em> throw ForwardedToErrorPage, it is the job of whoever calls this to do that if not within a
* JSP page (a JSP page can just return immediately). All text involved will be HTML-escaped.
*
* @param context The context that the error happened in (the JSP-defined pageContext, typically)
* @param i18n The i18n information
* @param e The exception that is being handled.
* @param label An i18n label for the error. This label should begin with "errormsg;".
* @param args Any extra args for i18n
* @throws IOFailure If the forward fails
*/
public static void forwardWithErrorMessage(PageContext context, I18n i18n, Throwable e, String label,
Object... args) {
// Note that we may not want to be to strict here
// as otherwise information could be lost.
ArgumentNotValid.checkNotNull(context, "context");
ArgumentNotValid.checkNotNull(I18N, "I18N");
ArgumentNotValid.checkNotNull(label, "label");
ArgumentNotValid.checkNotNull(args, "args");
String msg = HTMLUtils.escapeHtmlValues(i18n.getString(context.getResponse().getLocale(), label, args));
context.getRequest().setAttribute("message", msg + "\n" + e.getLocalizedMessage());
RequestDispatcher rd = context.getServletContext().getRequestDispatcher("/message.jsp");
final String errormsg = "Failed to forward on error " + msg;
try {
rd.forward(context.getRequest(), context.getResponse());
} catch (IOException e1) {
log.warn(errormsg, e1);
throw new IOFailure(errormsg, e1);
} catch (ServletException e1) {
log.warn(errormsg, e1);
throw new IOFailure(errormsg, e1);
}
}
/**
* Checks that the given parameters exist. If any of them do not exist, forwards to the error page and throws
* ForwardedToErrorPage.
*
* @param context The context of the current JSP page
* @param parameters List of parameters that must exist
* @throws IOFailure If the forward fails
* @throws ForwardedToErrorPage If a parameter is missing
*/
public static void forwardOnMissingParameter(PageContext context, String... parameters) throws ForwardedToErrorPage {
// Note that we may not want to be to strict here
// as otherwise information could be lost.
ArgumentNotValid.checkNotNull(context, "context");
ArgumentNotValid.checkNotNull(parameters, "parameters");
ServletRequest request = context.getRequest();
for (String parameter : parameters) {
String value = request.getParameter(parameter);
if (value == null) {
forwardWithErrorMessage(context, I18N, "errormsg;missing.parameter.0", parameter);
throw new ForwardedToErrorPage("Missing parameter '" + parameter + "'");
}
}
}
/**
* Checks that the given parameters exist and are not empty. If any of them are missing or empty, forwards to the
* error page and throws ForwardedToErrorPage. A parameter with only whitespace is considered empty.
*
* @param context The context of the current JSP page
* @param parameters List of parameters that must exist and be non-empty
* @throws IOFailure If the forward fails
* @throws ForwardedToErrorPage if a parameter was missing or empty
*/
public static void forwardOnEmptyParameter(PageContext context, String... parameters) {
// Note that we may not want to be to strict here
// as otherwise information could be lost.
ArgumentNotValid.checkNotNull(context, "context");
ArgumentNotValid.checkNotNull(parameters, "parameters");
forwardOnMissingParameter(context, parameters);
ServletRequest request = context.getRequest();
for (String parameter : parameters) {
String value = request.getParameter(parameter);
if (value.trim().length() == 0) {
forwardWithErrorMessage(context, I18N, "errormsg;empty.parameter.0", parameter);
throw new ForwardedToErrorPage("Empty parameter '" + parameter + "'");
}
}
}
/**
* Checks that the given parameter exists and is one of a set of values. If is is missing or doesn't equal one of
* the given values, forwards to the error page and throws ForwardedToErrorPage.
*
* @param context The context of the current JSP page
* @param parameter parameter that must exist
* @param legalValues legal values for the parameter
* @throws IOFailure If the forward fails
* @throws ForwardedToErrorPage if the parameter is none of the given values
*/
public static void forwardOnIllegalParameter(PageContext context, String parameter, String... legalValues)
throws ForwardedToErrorPage {
// Note that we may not want to be to strict here
// as otherwise information could be lost.
ArgumentNotValid.checkNotNull(context, "context");
ArgumentNotValid.checkNotNull(parameter, "parameter");
ArgumentNotValid.checkNotNull(legalValues, "legalValues");
forwardOnMissingParameter(context, parameter);
String value = context.getRequest().getParameter(parameter);
for (String legalValue : legalValues) {
if (value.equals(legalValue)) {
return;
}
}
forwardWithErrorMessage(context, I18N, "errormsg;illegal.value.0.for.parameter.1", value, parameter);
throw new ForwardedToErrorPage("Illegal value '" + value + "' for parameter '" + parameter + "'");
}
/**
* Parses a integer request parameter and checks that it lies within a given interval. If it doesn't, forwards to an
* error page and throws ForwardedToErrorPage.
*
* @param context The context this call happens in
* @param param A parameter to parse.
* @param minValue The minimum allowed value
* @param maxValue The maximum allowed value
* @return The value x parsed from the string, if minValue <= x <= maxValue
* @throws ForwardedToErrorPage if the parameter doesn't exist, is not a parseable integer, or doesn't lie within
* the limits.
*/
public static int parseAndCheckInteger(PageContext context, String param, int minValue, int maxValue)
throws ForwardedToErrorPage {
// Note that we may not want to be to strict here
// as otherwise information could be lost.
ArgumentNotValid.checkNotNull(context, "context");
ArgumentNotValid.checkNotNull(param, "param");
Locale loc = HTMLUtils.getLocaleObject(context);
forwardOnEmptyParameter(context, param);
int value;
String paramValue = context.getRequest().getParameter(param);
try {
value = NumberFormat.getInstance(loc).parse(paramValue).intValue();
if (value < minValue || value > maxValue) {
forwardWithErrorMessage(context, I18N, "errormsg;parameter.0.outside.range.1.to.2.3", param,
paramValue, minValue, maxValue);
throw new ForwardedToErrorPage("Parameter '" + param + "' should be between " + minValue + " and "
+ maxValue + " but is " + paramValue);
}
return value;
} catch (ParseException e) {
forwardWithErrorMessage(context, I18N, "errormsg;parameter.0.not.an.integer.1", param, paramValue);
throw new ForwardedToErrorPage("Invalid value " + paramValue + " for integer parameter '" + param + "'", e);
}
}
/**
* Parse an optionally present long-value from a request parameter.
*
* @param context The context of the web request.
* @param param The name of the parameter to parse.
* @param defaultValue A value to return if the parameter is not present (may be null).
* @return Parsed value or default value if the parameter is missing or empty. Null will only be returned if passed
* as the default value.
* @throws ForwardedToErrorPage if the parameter is present but not parseable as a long value.
*/
public static Long parseOptionalLong(PageContext context, String param, Long defaultValue) {
// Note that we may not want to be to strict here
// as otherwise information could be lost.
ArgumentNotValid.checkNotNull(context, "context");
ArgumentNotValid.checkNotNullOrEmpty(param, "String param");
Locale loc = HTMLUtils.getLocaleObject(context);
String paramValue = context.getRequest().getParameter(param);
return parseLong(loc, paramValue, param, defaultValue);
}
/**
* Parse an optionally present date-value from a request parameter.
*
* @param context The context of the web request.
* @param param The name of the parameter to parse
* @param format The format of the date, in the format defined by SimpleDateFormat
* @param defaultValue A value to return if the parameter is not present (may be null)
* @return Parsed value or default value if the parameter is missing or empty. Null will only be returned if passed
* as the default value.
* @throws ForwardedToErrorPage if the parameter is present but not parseable as a date
*/
public static Date parseOptionalDate(PageContext context, String param, String format, Date defaultValue) {
ArgumentNotValid.checkNotNullOrEmpty(param, "String param");
ArgumentNotValid.checkNotNullOrEmpty(format, "String format");
String paramValue = context.getRequest().getParameter(param);
if (paramValue != null && paramValue.trim().length() > 0) {
paramValue = paramValue.trim();
try {
return new SimpleDateFormat(format).parse(paramValue);
} catch (ParseException e) {
forwardWithErrorMessage(context, I18N, "errormsg;parameter.0.not.a.date.with.format.1.2", param,
format, paramValue);
throw new ForwardedToErrorPage("Invalid value " + paramValue + " for date parameter '" + param
+ "' with format '" + format + "'", e);
}
} else {
return defaultValue;
}
}
/**
* Parse an optionally present boolean from a request parameter.
*
* @param context The context of the web request.
* @param param The name of the parameter to parse
* @param defaultValue A value to return if the parameter is not present (may be null)
* @return Parsed value or default value if the parameter is missing or empty. Null will only be returned if passed
* as the default value.
*/
public static boolean parseOptionalBoolean(PageContext context, String param, boolean defaultValue) {
ArgumentNotValid.checkNotNullOrEmpty(param, "String param");
String paramValue = context.getRequest().getParameter(param);
if (paramValue != null && paramValue.trim().length() > 0) {
paramValue = paramValue.trim();
return Boolean.parseBoolean(paramValue);
} else {
return defaultValue;
}
}
/**
* Create a localized string representation of the given long.
*
* @param i A long integer
* @param context The given JSP context
* @return a localized string representation of the given long TODO Should this method throw ArgumentNotValid if the
* context is null
*/
public static String localiseLong(long i, PageContext context) {
NumberFormat nf = NumberFormat.getInstance(HTMLUtils.getLocaleObject(context));
return nf.format(i);
}
/**
* Create a localized string representation of the given long.
*
* @param i A long integer
* @param locale The given locale.
* @return a localized string representation of the given long
* TODO Should this method throw ArgumentNotValid if the locale is null
*/
public static String localiseLong(long i, Locale locale) {
NumberFormat nf = NumberFormat.getInstance(locale);
return nf.format(i);
}
/**
* Parse a given String for a long value.
*
* @param loc The given Locale.
* @param paramValue The given parameter value
* @param parameterName The given parameter name (used for debugging)
* @param defaultValue The default value for the parameter, in case the string cannot be parsed
* @return the long value found in the paramValue
*/
public static Long parseLong(Locale loc, String paramValue, String parameterName, Long defaultValue) {
ArgumentNotValid.checkNotNull(loc, "Locale loc");
ArgumentNotValid.checkNotNullOrEmpty(parameterName, "String parameterName");
if (paramValue != null && paramValue.trim().length() > 0) {
paramValue = paramValue.trim();
try {
return NumberFormat.getInstance(loc).parse(paramValue).longValue();
} catch (ParseException e) {
throw new ForwardedToErrorPage("Invalid value " + paramValue + " for integer parameter '"
+ parameterName + "'", e);
}
} else {
return defaultValue;
}
}
/**
* Parse Date to be displayed in 24 Hour format.
*
* @param timestamp The given date to parse.
* @return the String value found in the timestamp
*/
public static String parseDate(Date timestamp) {
SimpleDateFormat fmt = new SimpleDateFormat(DATE_FMT_STRING);
return timestamp != null ? fmt.format(timestamp) : "-";
}
}