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'>{@doc juneau-rest-server.HtmlDocAnnotation}
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().isEmpty())
075         nowrap(Boolean.valueOf(hd.nowrap()));
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 {@doc DefaultRestSvlVariables}
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 {@doc DefaultRestSvlVariables}
132    *       (e.g. <js>"$L{my.localized.variable}"</js>).
133    *    <li>
134    *       Supports {@doc juneau-marshall.URIs} (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 {@doc DefaultRestSvlVariables}
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 {@doc DefaultRestSvlVariables}
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 {@doc DefaultRestSvlVariables}
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 {@doc DefaultRestSvlVariables}
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 {@doc DefaultRestSvlVariables}
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 {@doc DefaultRestSvlVariables}
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 {@doc DefaultRestSvlVariables}
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 {@doc DefaultRestSvlVariables}
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 {@doc DefaultRestSvlVariables}
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 BasicHtmlDocTemplate} 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 {@doc DefaultRestSvlVariables}
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 BasicHtmlDocTemplate} 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 {@doc DefaultRestSvlVariables}
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}