001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.html; 014 015import org.apache.juneau.*; 016import org.apache.juneau.serializer.*; 017 018/** 019 * Serializes POJOs to HTTP responses as HTML documents. 020 * 021 * <h5 class='topic'>Media types</h5> 022 * 023 * Handles <code>Accept</code> types: <code><b>text/html</b></code> 024 * <p> 025 * Produces <code>Content-Type</code> types: <code><b>text/html</b></code> 026 * 027 * <h5 class='topic'>Description</h5> 028 * 029 * Same as {@link HtmlSerializer}, except wraps the response in <code><xt><html></code>, 030 * <code><xt><head></code>, and <code><xt><body></code> tags so that it can be rendered in a browser. 031 * 032 * <p> 033 * Configurable properties are typically specified via <ja>@RestResource.properties()</ja> and <ja>@RestMethod.properties()</ja> 034 * annotations, although they can also be set programmatically via the <code>RestResponse.setProperty()</code> method. 035 * 036 * <h5 class='section'>Example:</h5> 037 * <p class='bcode'> 038 * <ja>@RestResource</ja>( 039 * messages=<js>"nls/AddressBookResource"</js>, 040 * properties={ 041 * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_title</jsf>, value=<js>"$L{title}"</js>), 042 * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_description</jsf>, value=<js>"$L{description}"</js>), 043 * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_navlinks</jsf>, value=<js>"{options:'?method=OPTIONS',doc:'doc'}"</js>) 044 * } 045 * ) 046 * <jk>public class</jk> AddressBookResource <jk>extends</jk> BasicRestServletJena { 047 * </p> 048 * 049 * <p> 050 * Note that shortcut annotations are also provided for these particular settings: 051 * <p class='bcode'> 052 * <ja>@RestResource</ja>( 053 * messages=<js>"nls/AddressBookResource"</js>, 054 * title=<js>"$L{title}"</js>, 055 * description=<js>"$L{description}"</js>, 056 * htmldoc=<ja>@HtmlDoc</ja>( 057 * navlinks={ 058 * <js>"options: ?method=OPTIONS"</js>, 059 * <js>"doc: doc"</js> 060 * } 061 * ) 062 * ) 063 * </p> 064 * 065 * <p> 066 * The <code>$L{...}</code> variable represent localized strings pulled from the resource bundle identified by the 067 * <code>messages</code> annotation. 068 * <br>These variables are replaced at runtime based on the HTTP request locale. 069 * <br>Several built-in runtime variable types are defined, and the API can be extended to include user-defined variables. 070 */ 071public class HtmlDocSerializer extends HtmlStrippedDocSerializer { 072 073 //------------------------------------------------------------------------------------------------------------------- 074 // Configurable properties 075 //------------------------------------------------------------------------------------------------------------------- 076 077 private static final String PREFIX = "HtmlDocSerializer."; 078 079 /** 080 * Configuration property: Aside section contents. 081 * 082 * <h5 class='section'>Property:</h5> 083 * <ul> 084 * <li><b>Name:</b> <js>"HtmlDocSerializer.aside.ls"</js> 085 * <li><b>Data type:</b> <code>List<String></code> 086 * <li><b>Default:</b> empty list 087 * <li><b>Session-overridable:</b> <jk>true</jk> 088 * </ul> 089 * 090 * <h5 class='section'>Description:</h5> 091 * <p> 092 * Allows you to specify the contents of the aside section on the HTML page. 093 * The aside section floats on the right of the page for providing content supporting the serialized content of 094 * the page. 095 * 096 * <p> 097 * By default, the aside section is empty. 098 * 099 * <h5 class='section'>Example:</h5> 100 * <p class='bcode'> 101 * <ja>@RestResource</ja>( 102 * htmldoc=<ja>@HtmlDoc</ja>( 103 * aside={ 104 * <js>"<ul>"</js>, 105 * <js>" <li>Item 1"</js>, 106 * <js>" <li>Item 2"</js>, 107 * <js>" <li>Item 3"</js>, 108 * <js>"</ul>"</js> 109 * } 110 * ) 111 * ) 112 * </p> 113 */ 114 public static final String HTMLDOC_aside = PREFIX + "aside.ls"; 115 116 /** 117 * Configuration property: Footer section contents. 118 * 119 * <h5 class='section'>Property:</h5> 120 * <ul> 121 * <li><b>Name:</b> <js>"HtmlDocSerializer.footer.ls"</js> 122 * <li><b>Data type:</b> <code>List<String></code> 123 * <li><b>Default:</b> empty list 124 * <li><b>Session-overridable:</b> <jk>true</jk> 125 * </ul> 126 * 127 * <h5 class='section'>Description:</h5> 128 * <p> 129 * Allows you to specify the contents of the footer section on the HTML page. 130 * 131 * <p> 132 * By default, the footer section is empty. 133 * 134 * <h5 class='section'>Example:</h5> 135 * <p class='bcode'> 136 * <ja>@RestResource</ja>( 137 * htmldoc=<ja>@HtmlDoc</ja>( 138 * footer={ 139 * <js>"<b>This interface is great!</b>"</js> 140 * } 141 * ) 142 * ) 143 * </p> 144 */ 145 public static final String HTMLDOC_footer = PREFIX + "footer.ls"; 146 147 /** 148 * Configuration property: Additional head section content. 149 * 150 * <h5 class='section'>Property:</h5> 151 * <ul> 152 * <li><b>Name:</b> <js>"HtmlDocSerializer.head.ls"</js> 153 * <li><b>Data type:</b> <code>List<String></code> 154 * <li><b>Default:</b> empty list 155 * <li><b>Session-overridable:</b> <jk>true</jk> 156 * </ul> 157 * 158 * <h5 class='section'>Description:</h5> 159 * <p> 160 * Adds the specified HTML content to the head section of the page. 161 * 162 * <h5 class='section'>Example:</h5> 163 * <p class='bcode'> 164 * <ja>@RestResource</ja>( 165 * properties={ 166 * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_links</jsf>, 167 * value=<js>"['<link rel=\"icon\" href=\"htdocs/mypageicon.ico\">']"</js>) 168 * } 169 * ) 170 * </p> 171 * 172 * <p> 173 * A shortcut on <ja>@RestResource</ja> is also provided for this setting: 174 * <p class='bcode'> 175 * <ja>@RestResource</ja>( 176 * htmldoc=@HtmlDoc( 177 * head={ 178 * <js>"<link rel='icon' href='$U{servlet:/htdocs/mypageicon.ico}'>"</js> 179 * } 180 * ) 181 * ) 182 * </p> 183 */ 184 public static final String HTMLDOC_head = PREFIX + "head.ls"; 185 186 /** 187 * Configuration property: Header section contents. 188 * 189 * <h5 class='section'>Property:</h5> 190 * <ul> 191 * <li><b>Name:</b> <js>"HtmlDocSerializer.ls"</js> 192 * <li><b>Data type:</b> <code>List<String></code> 193 * <li><b>Default:</b> empty list 194 * <li><b>Session-overridable:</b> <jk>true</jk> 195 * </ul> 196 * 197 * <h5 class='section'>Description:</h5> 198 * <p> 199 * Allows you to override the contents of the header section on the HTML page. 200 * The header section normally contains the title and description at the top of the page. 201 * 202 * <h5 class='section'>Example:</h5> 203 * <p class='bcode'> 204 * <ja>@RestResource</ja>( 205 * htmldoc=<ja>@HtmlDoc</ja>( 206 * header={ 207 * <js>"<h1>My own header</h1>"</js> 208 * } 209 * ) 210 * ) 211 * </p> 212 */ 213 public static final String HTMLDOC_header = PREFIX + "header.ls"; 214 215 /** 216 * Configuration property: Nav section contents. 217 * 218 * <h5 class='section'>Property:</h5> 219 * <ul> 220 * <li><b>Name:</b> <js>"HtmlDocSerializer.nav.ls"</js> 221 * <li><b>Data type:</b> <code>List<String></code> 222 * <li><b>Default:</b> empty list 223 * <li><b>Session-overridable:</b> <jk>true</jk> 224 * </ul> 225 * 226 * <h5 class='section'>Description:</h5> 227 * <p> 228 * Allows you to override the contents of the nav section on the HTML page. 229 * The nav section normally contains the page links at the top of the page. 230 * 231 * <h5 class='section'>Example:</h5> 232 * <p class='bcode'> 233 * <ja>@RestResource</ja>( 234 * htmldoc=<ja>@HtmlDoc</ja>( 235 * nav={ 236 * <js>"<p class='special-navigation'>This is my special navigation content</p>"</js> 237 * } 238 * ) 239 * ) 240 * </p> 241 * 242 * <p> 243 * When this property is specified, the {@link #HTMLDOC_navlinks} property is ignored. 244 */ 245 public static final String HTMLDOC_nav = PREFIX + "nav.ls"; 246 247 /** 248 * Configuration property: Page navigation links. 249 * 250 * <h5 class='section'>Property:</h5> 251 * <ul> 252 * <li><b>Name:</b> <js>"HtmlDocSerializer.navlinks.ls"</js> 253 * <li><b>Data type:</b> <code>List<String></code> 254 * <li><b>Default:</b> empty list 255 * <li><b>Session-overridable:</b> <jk>true</jk> 256 * </ul> 257 * 258 * <h5 class='section'>Description:</h5> 259 * <p> 260 * Adds a list of hyperlinks immediately under the title and description but above the content of the page. 261 * 262 * <p> 263 * This can be used to provide convenient hyperlinks when viewing the REST interface from a browser. 264 * 265 * <p> 266 * The value is an array of strings with two possible values: 267 * <ul> 268 * <li>A key-value pair representing a hyperlink label and href: 269 * <br><js>"google: http://google.com"</js> 270 * <li>Arbitrary HTML. 271 * </ul> 272 * 273 * <p> 274 * Relative URLs are considered relative to the servlet path. 275 * For example, if the servlet path is <js>"http://localhost/myContext/myServlet"</js>, and the 276 * URL is <js>"foo"</js>, the link becomes <js>"http://localhost/myContext/myServlet/foo"</js>. 277 * Absolute (<js>"/myOtherContext/foo"</js>) and fully-qualified (<js>"http://localhost2/foo"</js>) URLs 278 * can also be used in addition to various other protocols specified by {@link UriResolver} such as 279 * <js>"servlet:/..."</js>. 280 * 281 * <h5 class='section'>Example:</h5> 282 * <p> 283 * The <code>AddressBookResource</code> sample class uses this property... 284 * <p class='bcode'> 285 * <ja>@RestResource</ja>( 286 * properties={ 287 * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_navlinks</jsf>, 288 * value=<js>"['options: ?method=OPTIONS', 'doc: doc']"</js>) 289 * } 290 * ) 291 * <jk>public class</jk> AddressBookResource <jk>extends</jk> BasicRestServletJena { 292 * </p> 293 * 294 * <p> 295 * ...to produce this list of links on the HTML page... 296 * <img class='bordered' src='doc-files/HTML_LINKS.png'> 297 * 298 * <p> 299 * A shortcut on <ja>@RestResource</ja> is also provided for this setting: 300 * <p class='bcode'> 301 * <ja>@RestResource</ja>( 302 * htmldoc=@HtmlDoc( 303 * navlinks={ 304 * <js>"options: ?method=OPTIONS"</js>, 305 * <js>"doc: doc"</js> 306 * } 307 * ) 308 * ) 309 * <jk>public class</jk> AddressBookResource <jk>extends</jk> BasicRestServletJena { 310 * </p> 311 */ 312 public static final String HTMLDOC_navlinks = PREFIX + "navlinks.ls"; 313 314 /** 315 * Configuration property: Add to the {@link #HTMLDOC_navlinks} property. 316 */ 317 public static final String HTMLDOC_navlinks_add = PREFIX + "navlinks.ls/add"; 318 319 /** 320 * Configuration property: No-results message. 321 * 322 * <h5 class='section'>Property:</h5> 323 * <ul> 324 * <li><b>Name:</b> <js>"HtmlDocSerializer.noResultsMessage.s"</js> 325 * <li><b>Data type:</b> <code>String</code> 326 * <li><b>Default:</b> <js>"<p>no results</p>"</js> 327 * <li><b>Session-overridable:</b> <jk>true</jk> 328 * </ul> 329 * 330 * <h5 class='section'>Description:</h5> 331 * <p> 332 * Allows you to specify the string message used when trying to serialize an empty array or empty list. 333 * 334 * <h5 class='section'>Example:</h5> 335 * <p class='bcode'> 336 * <ja>@RestResource</ja>( 337 * htmldoc=<ja>@HtmlDoc</ja>( 338 * noResultsMessage=<js>"<b>This interface is great!</b>"</js> 339 * ) 340 * ) 341 * </p> 342 * 343 * <p> 344 * A value of <js>"NONE"</js> can be used to represent no value to differentiate it from an empty string. 345 */ 346 public static final String HTMLDOC_noResultsMessage = PREFIX + "noResultsMessage.s"; 347 348 /** 349 * Configuration property: Prevent word wrap on page. 350 * 351 * <h5 class='section'>Property:</h5> 352 * <ul> 353 * <li><b>Name:</b> <js>"HtmlDocSerializer.nowrap.b"</js> 354 * <li><b>Data type:</b> <code>Boolean</code> 355 * <li><b>Default:</b> <jk>false</jk> 356 * <li><b>Session-overridable:</b> <jk>true</jk> 357 * </ul> 358 * 359 * <h5 class='section'>Description:</h5> 360 * <p> 361 * Adds <js>"* {white-space:nowrap}"</js> to the CSS instructions on the page to prevent word wrapping. 362 */ 363 public static final String HTMLDOC_nowrap = PREFIX + "nowrap.b"; 364 365 /** 366 * Configuration property: Javascript code. 367 * 368 * <h5 class='section'>Property:</h5> 369 * <ul> 370 * <li><b>Name:</b> <js>"HtmlDocSerializer.script.ls"</js> 371 * <li><b>Data type:</b> <code>List<String></code> 372 * <li><b>Default:</b> empty list 373 * <li><b>Session-overridable:</b> <jk>true</jk> 374 * </ul> 375 * 376 * <h5 class='section'>Description:</h5> 377 * <p> 378 * Adds the specified Javascript code to the HTML page. 379 * 380 * <h5 class='section'>Example:</h5> 381 * <p class='bcode'> 382 * <ja>@RestResource</ja>( 383 * properties={ 384 * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_script</jsf>, 385 * value=<js>"alert('hello!');"</js>) 386 * } 387 * ) 388 * </p> 389 * 390 * <p> 391 * A shortcut on <ja>@RestResource</ja> is also provided for this setting: 392 * <p class='bcode'> 393 * <ja>@RestResource</ja>( 394 * htmldoc=@HtmlDoc( 395 * script={ 396 * <js>"alert('hello!');"</js> 397 * } 398 * ) 399 * ) 400 * </p> 401 */ 402 public static final String HTMLDOC_script = PREFIX + "script.ls"; 403 404 /** 405 * Configuration property: Add to the {@link #HTMLDOC_script} property. 406 */ 407 public static final String HTMLDOC_script_add = PREFIX + "script.ls/add"; 408 409 /** 410 * Configuration property: CSS style code. 411 * 412 * <h5 class='section'>Property:</h5> 413 * <ul> 414 * <li><b>Name:</b> <js>"HtmlDocSerializer.style.ls"</js> 415 * <li><b>Data type:</b> <code>List<String></code> 416 * <li><b>Default:</b> empty list 417 * <li><b>Session-overridable:</b> <jk>true</jk> 418 * </ul> 419 * 420 * <h5 class='section'>Description:</h5> 421 * <p> 422 * Adds the specified CSS instructions to the HTML page. 423 * 424 * <h5 class='section'>Example:</h5> 425 * <p class='bcode'> 426 * <ja>@RestResource</ja>( 427 * properties={ 428 * <ja>@Property</ja>(name=HtmlDocSerializer.<jsf>HTMLDOC_style</jsf>, 429 * value=<js>"h3 { color: red; }\nh5 { font-weight: bold; }"</js>) 430 * } 431 * ) 432 * </p> 433 * 434 * <p> 435 * A shortcut on <ja>@RestResource</ja> is also provided for this setting: 436 * <p class='bcode'> 437 * <ja>@RestResource</ja>( 438 * htmldoc=@HtmlDoc( 439 * style={ 440 * <js>"h3 { color: red; }"</js>, 441 * <js>"h5 { font-weight: bold; }"</js> 442 * } 443 * ) 444 * ) 445 * </p> 446 */ 447 public static final String HTMLDOC_style = PREFIX + "style.ls"; 448 449 /** 450 * Configuration property: Add to the {@link #HTMLDOC_style} property. 451 */ 452 public static final String HTMLDOC_style_add = PREFIX + "style.ls/add"; 453 454 /** 455 * Configuration property: Stylesheet import URLs. 456 * 457 * <h5 class='section'>Property:</h5> 458 * <ul> 459 * <li><b>Name:</b> <js>"HtmlDocSerializer.stylesheet.ls"</js> 460 * <li><b>Data type:</b> <code>List<String></code> 461 * <li><b>Default:</b> empty list 462 * <li><b>Session-overridable:</b> <jk>true</jk> 463 * </ul> 464 * 465 * <h5 class='section'>Description:</h5> 466 * <p> 467 * Adds a link to the specified stylesheet URL. 468 * 469 * <p> 470 * Note that this stylesheet is controlled by the <code><ja>@RestResource</ja>.stylesheet()</code> annotation. 471 */ 472 public static final String HTMLDOC_stylesheet = PREFIX + "stylesheet.ls"; 473 474 /** 475 * Configuration property: Add to the {@link #HTMLDOC_stylesheet} property. 476 */ 477 public static final String HTMLDOC_stylesheet_add = PREFIX + "stylesheet.ls/add"; 478 479 /** 480 * Configuration property: HTML document template. 481 * 482 * <h5 class='section'>Property:</h5> 483 * <ul> 484 * <li><b>Name:</b> <js>"HtmlDocSerializer.template.c"</js> 485 * <li><b>Data type:</b> <code>Class<? <jk>extends</jk> HtmlDocTemplate></code> 486 * <li><b>Default:</b> <code>HtmlDocTemplateBasic.<jk>class</jk></code> 487 * <li><b>Session-overridable:</b> <jk>true</jk> 488 * </ul> 489 * 490 * <h5 class='section'>Description:</h5> 491 * <p> 492 * Specifies the template to use for serializing the page. 493 * 494 * <p> 495 * By default, the {@link HtmlDocTemplateBasic} class is used to construct the contents of the HTML page, but 496 * can be overridden with your own custom implementation class. 497 * 498 * <h5 class='section'>Example:</h5> 499 * <p class='bcode'> 500 * <ja>@RestResource</ja>( 501 * htmldoc=@HtmlDoc( 502 * template=MySpecialDocTemplate.<jk>class</jk> 503 * ) 504 * ) 505 * </p> 506 */ 507 public static final String HTMLDOC_template = PREFIX + "template.c"; 508 509 510 //------------------------------------------------------------------------------------------------------------------- 511 // Predefined instances 512 //------------------------------------------------------------------------------------------------------------------- 513 514 /** Default serializer, all default settings. */ 515 public static final HtmlDocSerializer DEFAULT = new HtmlDocSerializer(PropertyStore.DEFAULT); 516 517 518 //------------------------------------------------------------------------------------------------------------------- 519 // Instance 520 //------------------------------------------------------------------------------------------------------------------- 521 522 final String[] style, stylesheet, script, navlinks, head, header, nav, aside, footer; 523 final String noResultsMessage; 524 final boolean nowrap; 525 final HtmlDocTemplate template; 526 527 /** 528 * Constructor. 529 * 530 * @param ps The property store containing all the settings for this object. 531 */ 532 public HtmlDocSerializer(PropertyStore ps) { 533 this(ps, "text/html"); 534 } 535 536 /** 537 * Constructor. 538 * 539 * @param ps 540 * The property store containing all the settings for this object. 541 * @param produces 542 * The media type that this serializer produces. 543 * @param accept 544 * The accept media types that the serializer can handle. 545 * <p> 546 * Can contain meta-characters per the <code>media-type</code> specification of 547 * <a class="doclink" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1">RFC2616/14.1</a> 548 * <p> 549 * If empty, then assumes the only media type supported is <code>produces</code>. 550 * <p> 551 * For example, if this serializer produces <js>"application/json"</js> but should handle media types of 552 * <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be: 553 * <p class='bcode'> 554 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json"</js>, <js>"text/json"</js>); 555 * </p> 556 * <br>...or... 557 * <p class='bcode'> 558 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*​/json"</js>); 559 * </p> 560 */ 561 public HtmlDocSerializer(PropertyStore ps, String produces, String...accept) { 562 super(ps, produces, accept); 563 style = getArrayProperty(HTMLDOC_style, String.class); 564 stylesheet = getArrayProperty(HTMLDOC_stylesheet, String.class); 565 script = getArrayProperty(HTMLDOC_script, String.class); 566 head = getArrayProperty(HTMLDOC_head, String.class); 567 header = getArrayProperty(HTMLDOC_header, String.class); 568 nav = getArrayProperty(HTMLDOC_nav, String.class); 569 aside = getArrayProperty(HTMLDOC_aside, String.class); 570 footer = getArrayProperty(HTMLDOC_footer, String.class); 571 nowrap = getBooleanProperty(HTMLDOC_nowrap, false); 572 navlinks = getArrayProperty(HTMLDOC_navlinks, String.class); 573 noResultsMessage = getStringProperty(HTMLDOC_noResultsMessage, "<p>no results</p>"); 574 template = getInstanceProperty(HTMLDOC_template, HtmlDocTemplate.class, HtmlDocTemplateBasic.class); 575 } 576 577 @Override /* Serializer */ 578 public HtmlDocSerializerSession createSession(SerializerSessionArgs args) { 579 return new HtmlDocSerializerSession(this, args); 580 } 581 582 @Override /* Context */ 583 public ObjectMap asMap() { 584 return super.asMap() 585 .append("HtmlDocSerializer", new ObjectMap() 586 .append("header", header) 587 .append("nav", nav) 588 .append("navlinks", navlinks) 589 .append("aside", aside) 590 .append("footer", footer) 591 .append("style", style) 592 .append("head", head) 593 .append("stylesheet", stylesheet) 594 .append("nowrap", nowrap) 595 .append("template", template) 596 .append("noResultsMessage", noResultsMessage) 597 ); 598 } 599}