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.rest; 014 015import static org.apache.juneau.html.HtmlDocSerializer.*; 016import static org.apache.juneau.internal.StringUtils.*; 017 018import java.util.*; 019import java.util.regex.*; 020 021import org.apache.juneau.*; 022import org.apache.juneau.html.*; 023import org.apache.juneau.rest.annotation.*; 024import org.apache.juneau.utils.*; 025 026/** 027 * Programmatic interface for setting properties used by the HtmlDoc serializer. 028 * 029 * <p> 030 * Basically just a convenience wrapper around the servlet or method level properties for setting properties defined 031 * by the {@link HtmlDocSerializer} class. 032 * 033 * <p> 034 * This class is instantiated through the following methods: 035 * <ul> 036 * <li class='jm'>{@link RestContextBuilder#getHtmlDocBuilder()} - Set values programmatically during servlet initialization. 037 * <li class='jm'>{@link RestResponse#getHtmlDocBuilder()} - Set values programmatically during a REST request. 038 * </ul> 039 * 040 * <h5 class='section'>See Also:</h5> 041 * <ul> 042 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-rest-server.HtmlDocAnnotation">Overview > juneau-rest-server > @HtmlDoc</a> 043 * </ul> 044 */ 045public class HtmlDocBuilder { 046 047 private final ObjectMap properties; 048 049 HtmlDocBuilder(ObjectMap properties) { 050 this.properties = properties; 051 } 052 053 void process(HtmlDoc hd) { 054 if (hd.header().length > 0) 055 header((Object[])hd.header()); 056 if (hd.nav().length > 0) 057 nav((Object[])hd.nav()); 058 if (hd.aside().length > 0) 059 aside((Object[])hd.aside()); 060 if (hd.footer().length > 0) 061 footer((Object[])hd.footer()); 062 if (hd.style().length > 0) 063 style((Object[])hd.style()); 064 if (hd.script().length > 0) 065 script((Object[])hd.script()); 066 if (hd.navlinks().length > 0) 067 navlinks((Object[])hd.navlinks()); 068 if (hd.head().length > 0) 069 head((Object[])hd.head()); 070 if (hd.stylesheet().length > 0) 071 stylesheet((Object[])hd.stylesheet()); 072 if (! hd.noResultsMessage().isEmpty()) 073 noResultsMessage(hd.noResultsMessage()); 074 if (hd.nowrap()) 075 nowrap(true); 076 if (hd.template() != HtmlDocTemplate.class) 077 template(hd.template()); 078 } 079 080 /** 081 * Sets the HTML header section contents. 082 * 083 * <p> 084 * The page header normally contains the title and description, but this value can be used to override the contents 085 * to be whatever you want. 086 * 087 * <h5 class='section'>Notes:</h5> 088 * <ul class='spaced-list'> 089 * <li> 090 * The format of this value is HTML. 091 * <li> 092 * When a value is specified, the {@link #navlinks(Object...)} value will be ignored. 093 * <li> 094 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 095 * (e.g. <js>"$L{my.localized.variable}"</js>). 096 * <li> 097 * A value of <js>"INHERIT"</js> means copy the values from the parent. 098 * <li> 099 * A value of <js>"NONE"</js> can be used to force no value. 100 * <li> 101 * This is the programmatic equivalent to the {@link HtmlDoc#header() @HtmlDoc.header()} annotation. 102 * </ul> 103 * 104 * @param value 105 * The HTML header section contents. 106 * Object will be converted to a string using {@link Object#toString()}. 107 * <p> 108 * <ul class='doctree'> 109 * <li class='info'> 110 * <b>Tip:</b> Use {@link StringMessage} to generate value with delayed serialization so as not to 111 * waste string concatenation cycles on non-HTML views. 112 * </ul> 113 * @return This object (for method chaining). 114 */ 115 public HtmlDocBuilder header(Object...value) { 116 return set(HTMLDOC_header, resolveList(value, properties.getStringArray(HTMLDOC_header))); 117 } 118 119 /** 120 * Sets the links in the HTML nav section. 121 * 122 * <p> 123 * The page links are positioned immediately under the title and text. 124 * 125 * <h5 class='section'>Notes:</h5> 126 * <ul class='spaced-list'> 127 * <li> 128 * The format of this value is a lax-JSON map of key/value pairs where the keys are the link text and the values are 129 * relative (to the servlet) or absolute URLs. 130 * <li> 131 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 132 * (e.g. <js>"$L{my.localized.variable}"</js>). 133 * <li> 134 * Supports <a class="doclink" href="../../../../overview-summary.html#juneau-marshall.URIs">URI resolution</a> (e.g. <js>"servlet:/..."</js>, <js>"request:/..."</js>). 135 * <li> 136 * A value of <js>"INHERIT"</js> means copy the values from the parent. 137 * <li> 138 * A value of <js>"NONE"</js> can be used to force no value. 139 * <li> 140 * This is the programmatic equivalent to the {@link HtmlDoc#navlinks() @HtmlDoc.navlinks()} annotation. 141 * </ul> 142 * 143 * @param value 144 * The HTML nav section links links. 145 * <p> 146 * <ul class='doctree'> 147 * <li class='info'> 148 * <b>Tip:</b> Use {@link StringMessage} to generate value with delayed serialization so as not to 149 * waste string concatenation cycles on non-HTML views. 150 * </ul> 151 * @return This object (for method chaining). 152 */ 153 public HtmlDocBuilder navlinks(Object...value) { 154 return set(HTMLDOC_navlinks, resolveLinks(value, properties.getStringArray(HTMLDOC_navlinks))); 155 } 156 157 /** 158 * Sets the HTML nav section contents. 159 * 160 * <p> 161 * The nav section of the page contains the links. 162 * 163 * 164 * <h5 class='section'>Notes:</h5> 165 * <ul class='spaced-list'> 166 * <li> 167 * The format of this value is HTML. 168 * <li> 169 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 170 * (e.g. <js>"$L{my.localized.variable}"</js>). 171 * <li> 172 * When a value is specified, the {@link #navlinks(Object[])} value will be ignored. 173 * <li> 174 * A value of <js>"INHERIT"</js> means copy the values from the parent. 175 * <li> 176 * A value of <js>"NONE"</js> can be used to force no value. 177 * <li> 178 * This is the programmatic equivalent to the {@link HtmlDoc#nav() @HtmlDoc.nav()} annotation. 179 * </ul> 180 * 181 * @param value 182 * The HTML nav section contents. 183 * Object will be converted to a string using {@link Object#toString()}. 184 * <p> 185 * <ul class='doctree'> 186 * <li class='info'> 187 * <b>Tip:</b> Use {@link StringMessage} to generate value with delayed serialization so as not to 188 * waste string concatenation cycles on non-HTML views. 189 * </ul> 190 * @return This object (for method chaining). 191 */ 192 public HtmlDocBuilder nav(Object...value) { 193 return set(HTMLDOC_nav, resolveList(value, properties.getStringArray(HTMLDOC_nav))); 194 } 195 196 /** 197 * Sets the HTML aside section contents. 198 * 199 * <p> 200 * The aside section typically floats on the right side of the page. 201 * 202 * <h5 class='section'>Notes:</h5> 203 * <ul class='spaced-list'> 204 * <li> 205 * The format of this value is HTML. 206 * <li> 207 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 208 * (e.g. <js>"$L{my.localized.variable}"</js>). 209 * <li> 210 * A value of <js>"INHERIT"</js> means copy the values from the parent. 211 * <li> 212 * A value of <js>"NONE"</js> can be used to force no value. 213 * <li> 214 * This is the programmatic equivalent to the {@link HtmlDoc#aside() @HtmlDoc.aside()} annotation. 215 * </ul> 216 * 217 * @param value 218 * The HTML aside section contents. 219 * Object will be converted to a string using {@link Object#toString()}. 220 * <p> 221 * <ul class='doctree'> 222 * <li class='info'> 223 * <b>Tip:</b> Use {@link StringMessage} to generate value with delayed serialization so as not to waste 224 * string concatenation cycles on non-HTML views. 225 * </ul> 226 * @return This object (for method chaining). 227 */ 228 public HtmlDocBuilder aside(Object...value) { 229 return set(HTMLDOC_aside, resolveList(value, properties.getStringArray(HTMLDOC_aside))); 230 } 231 232 /** 233 * Sets the HTML footer section contents. 234 * 235 * <p> 236 * The footer section typically floats on the bottom of the page. 237 * 238 * <h5 class='section'>Notes:</h5> 239 * <ul class='spaced-list'> 240 * <li> 241 * The format of this value is HTML. 242 * <li> 243 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 244 * (e.g. <js>"$L{my.localized.variable}"</js>). 245 * <li> 246 * A value of <js>"INHERIT"</js> means copy the values from the parent. 247 * <li> 248 * A value of <js>"NONE"</js> can be used to force no value. 249 * <li> 250 * This is the programmatic equivalent to the {@link HtmlDoc#footer() @HtmlDoc.footer()} annotation. 251 * </ul> 252 * 253 * @param value 254 * The HTML footer section contents. 255 * Object will be converted to a string using {@link Object#toString()}. 256 * <p> 257 * <ul class='doctree'> 258 * <li class='info'> 259 * <b>Tip:</b> Use {@link StringMessage} to generate value with delayed serialization so as not to 260 * waste string concatenation cycles on non-HTML views. 261 * </ul> 262 * @return This object (for method chaining). 263 */ 264 public HtmlDocBuilder footer(Object...value) { 265 return set(HTMLDOC_footer, resolveList(value, properties.getStringArray(HTMLDOC_footer))); 266 } 267 268 /** 269 * Sets the HTML CSS style section contents. 270 * 271 * <h5 class='section'>Notes:</h5> 272 * <ul class='spaced-list'> 273 * <li> 274 * The format of this value is CSS. 275 * <li> 276 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 277 * (e.g. <js>"$L{my.localized.variable}"</js>). 278 * <li> 279 * A value of <js>"INHERIT"</js> means copy the values from the parent. 280 * <li> 281 * A value of <js>"NONE"</js> can be used to force no value. 282 * <li> 283 * This is the programmatic equivalent to the {@link HtmlDoc#style() @HtmlDoc.style()} annotation. 284 * </ul> 285 * 286 * @param value 287 * The HTML CSS style section contents. 288 * Object will be converted to a string using {@link Object#toString()}. 289 * <p> 290 * <ul class='doctree'> 291 * <li class='info'> 292 * <b>Tip:</b> Use {@link StringMessage} to generate value with delayed serialization so as not to 293 * waste string concatenation cycles on non-HTML views. 294 * </ul> 295 * @return This object (for method chaining). 296 */ 297 public HtmlDocBuilder style(Object...value) { 298 return set(HTMLDOC_style, resolveList(value, properties.getStringArray(HTMLDOC_style))); 299 } 300 301 /** 302 * Sets the CSS URL in the HTML CSS style section. 303 * 304 * <p> 305 * Specifies the URL to the stylesheet to add as a link in the style tag in the header. 306 * 307 * <h5 class='section'>Notes:</h5> 308 * <ul class='spaced-list'> 309 * <li> 310 * The format of this value is a comma-delimited list of URLs. 311 * <li> 312 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 313 * (e.g. <js>"$L{my.localized.variable}"</js>). 314 * <li> 315 * This is the programmatic equivalent to the {@link HtmlDoc#stylesheet() @HtmlDoc.stylesheet()} annotation. 316 * </ul> 317 * 318 * @param value 319 * The CSS URL in the HTML CSS style section. 320 * Object will be converted to a string using {@link Object#toString()}. 321 * <p> 322 * <ul class='doctree'> 323 * <li class='info'> 324 * <b>Tip:</b> Use {@link StringMessage} to generate value with delayed serialization so as not to 325 * waste string concatenation cycles on non-HTML views. 326 * </ul> 327 * @return This object (for method chaining). 328 */ 329 public HtmlDocBuilder stylesheet(Object...value) { 330 return set(HTMLDOC_stylesheet, resolveSet(value, properties.getStringArray(HTMLDOC_nav))); 331 } 332 333 /** 334 * Sets the HTML script section contents. 335 * 336 * <h5 class='section'>Notes:</h5> 337 * <ul class='spaced-list'> 338 * <li> 339 * The format of this value is Javascript. 340 * <li> 341 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 342 * (e.g. <js>"$L{my.localized.variable}"</js>). 343 * <li> 344 * A value of <js>"INHERIT"</js> means copy the values from the parent. 345 * <li> 346 * A value of <js>"NONE"</js> can be used to force no value. 347 * <li> 348 * This is the programmatic equivalent to the {@link HtmlDoc#script() @HtmlDoc.script()} annotation. 349 * </ul> 350 * 351 * @param value 352 * The HTML script section contents. 353 * Object will be converted to a string using {@link Object#toString()}. 354 * <p> 355 * <ul class='doctree'> 356 * <li class='info'> 357 * <b>Tip:</b> Use {@link StringMessage} to generate value with delayed serialization so as not to 358 * waste string concatenation cycles on non-HTML views. 359 * </ul> 360 * @return This object (for method chaining). 361 */ 362 public HtmlDocBuilder script(Object...value) { 363 return set(HTMLDOC_script, resolveList(value, properties.getStringArray(HTMLDOC_script))); 364 } 365 366 /** 367 * Sets the HTML head section contents. 368 * 369 * <h5 class='section'>Notes:</h5> 370 * <ul class='spaced-list'> 371 * <li> 372 * The format of this value is HTML. 373 * <li> 374 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 375 * (e.g. <js>"$L{my.localized.variable}"</js>). 376 * <li> 377 * A value of <js>"INHERIT"</js> means copy the values from the parent. 378 * <li> 379 * A value of <js>"NONE"</js> can be used to force no value. 380 * <li> 381 * This is the programmatic equivalent to the {@link HtmlDoc#head() @HtmlDoc.head()} annotation. 382 * </ul> 383 * 384 * @param value 385 * The HTML head section contents. 386 * <p> 387 * <ul class='doctree'> 388 * <li class='info'> 389 * <b>Tip:</b> Use {@link StringMessage} to generate value with delayed serialization so as not to 390 * waste string concatenation cycles on non-HTML views. 391 * </ul> 392 * @return This object (for method chaining). 393 */ 394 public HtmlDocBuilder head(Object...value) { 395 return set(HTMLDOC_head, resolveList(value, properties.getStringArray(HTMLDOC_head))); 396 } 397 398 /** 399 * Shorthand method for forcing the rendered HTML content to be no-wrap. 400 * 401 * <h5 class='section'>Notes:</h5> 402 * <ul class='spaced-list'> 403 * <li> 404 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 405 * (e.g. <js>"$L{my.localized.variable}"</js>). 406 * <li> 407 * This is the programmatic equivalent to the {@link HtmlDoc#nowrap() @HtmlDoc.nowrap()} annotation. 408 * </ul> 409 * 410 * @param value The new nowrap setting. 411 * @return This object (for method chaining). 412 */ 413 public HtmlDocBuilder nowrap(boolean value) { 414 return set(HTMLDOC_nowrap, value); 415 } 416 417 /** 418 * Specifies the text to display when serializing an empty array or collection. 419 * 420 * <h5 class='section'>Notes:</h5> 421 * <ul class='spaced-list'> 422 * <li> 423 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 424 * (e.g. <js>"$L{my.localized.variable}"</js>). 425 * <li> 426 * This is the programmatic equivalent to the {@link HtmlDoc#noResultsMessage() @HtmlDoc.noResultsMessage()} annotation. 427 * </ul> 428 * 429 * @param value The text to display when serializing an empty array or collection. 430 * @return This object (for method chaining). 431 */ 432 public HtmlDocBuilder noResultsMessage(Object value) { 433 return set(HTMLDOC_noResultsMessage, value); 434 } 435 436 /** 437 * Specifies the template class to use for rendering the HTML page. 438 * 439 * <p> 440 * By default, uses {@link HtmlDocTemplateBasic} to render the contents, although you can provide your own custom 441 * renderer or subclasses from the basic class to have full control over how the page is rendered. 442 * 443 * <h5 class='section'>Notes:</h5> 444 * <ul class='spaced-list'> 445 * <li> 446 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 447 * (e.g. <js>"$L{my.localized.variable}"</js>). 448 * <li> 449 * This is the programmatic equivalent to the {@link HtmlDoc#template() @HtmlDoc.template()} annotation. 450 * </ul> 451 * 452 * @param value The HTML page template to use to render the HTML page. 453 * @return This object (for method chaining). 454 */ 455 public HtmlDocBuilder template(Class<? extends HtmlDocTemplate> value) { 456 return set(HTMLDOC_template, value); 457 } 458 459 /** 460 * Specifies the template class to use for rendering the HTML page. 461 * 462 * <p> 463 * By default, uses {@link HtmlDocTemplateBasic} to render the contents, although you can provide your own custom 464 * renderer or subclasses from the basic class to have full control over how the page is rendered. 465 * 466 * <h5 class='section'>Notes:</h5> 467 * <ul class='spaced-list'> 468 * <li> 469 * Supports <a class="doclink" href="../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 470 * (e.g. <js>"$L{my.localized.variable}"</js>). 471 * <li> 472 * This is the programmatic equivalent to the {@link HtmlDoc#template() @HtmlDoc.template()} annotation. 473 * </ul> 474 * 475 * @param value The HTML page template to use to render the HTML page. 476 * @return This object (for method chaining). 477 */ 478 public HtmlDocBuilder template(HtmlDocTemplate value) { 479 return set(HTMLDOC_template, value); 480 } 481 482 private static final Pattern INDEXED_LINK_PATTERN = Pattern.compile("(?s)(\\S*)\\[(\\d+)\\]\\:(.*)"); 483 484 private static String[] resolveLinks(Object[] value, String[] prev) { 485 List<String> list = new ArrayList<>(); 486 for (Object v : value) { 487 String s = asString(v); 488 if ("INHERIT".equals(s)) { 489 list.addAll(Arrays.asList(prev)); 490 } else if (s.indexOf('[') != -1 && INDEXED_LINK_PATTERN.matcher(s).matches()) { 491 Matcher lm = INDEXED_LINK_PATTERN.matcher(s); 492 lm.matches(); 493 String key = lm.group(1); 494 int index = Math.min(list.size(), Integer.parseInt(lm.group(2))); 495 String remainder = lm.group(3); 496 list.add(index, key.isEmpty() ? remainder : key + ":" + remainder); 497 } else { 498 list.add(s); 499 } 500 } 501 return list.toArray(new String[list.size()]); 502 } 503 504 private static String[] resolveSet(Object[] value, String[] prev) { 505 Set<String> set = new HashSet<>(); 506 for (Object v : value) { 507 String s = asString(v); 508 if ("INHERIT".equals(s)) { 509 if (prev != null) 510 set.addAll(Arrays.asList(prev)); 511 } else if ("NONE".equals(s)) { 512 return new String[0]; 513 } else { 514 set.add(s); 515 } 516 } 517 return set.toArray(new String[set.size()]); 518 } 519 520 private static String[] resolveList(Object[] value, String[] prev) { 521 Set<String> set = new LinkedHashSet<>(); 522 for (Object v : value) { 523 String s = asString(v); 524 if ("INHERIT".equals(s)) { 525 if (prev != null) 526 set.addAll(Arrays.asList(prev)); 527 } else if ("NONE".equals(s)) { 528 return new String[0]; 529 } else { 530 set.add(s); 531 } 532 } 533 return set.toArray(new String[set.size()]); 534 } 535 536 private HtmlDocBuilder set(String key, Object value) { 537 properties.put(key, value); 538 return this; 539 } 540}