Clean up how HtmlDoc properties are defined.

Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/9c746e64
Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/9c746e64
Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/9c746e64

Branch: refs/heads/master
Commit: 9c746e647280eeb49cd78b5956eb5c1250cf71f3
Parents: d767cb6
Author: JamesBognar <[email protected]>
Authored: Mon Oct 9 18:57:45 2017 -0400
Committer: JamesBognar <[email protected]>
Committed: Mon Oct 9 18:57:45 2017 -0400

----------------------------------------------------------------------
 .../juneau/svl/ResolvingObjectMapTest.java      | 121 +++++
 .../main/java/org/apache/juneau/ObjectMap.java  |  19 +-
 .../juneau/html/HtmlDocSerializerContext.java   |  61 +--
 .../juneau/html/HtmlDocSerializerSession.java   |  20 +-
 .../juneau/html/HtmlDocTemplateBasic.java       |  61 ++-
 .../juneau/serializer/SerializerWriter.java     |  17 +
 .../apache/juneau/svl/ResolvingObjectMap.java   | 119 +++++
 .../apache/juneau/rest/test/HtmlDocTest.java    |  20 +-
 .../apache/juneau/rest/test/PropertiesTest.java |  10 -
 .../java/org/apache/juneau/rest/CallMethod.java |  63 +--
 .../org/apache/juneau/rest/HtmlDocBuilder.java  | 151 +++++-
 .../org/apache/juneau/rest/HtmlDocConfig.java   | 456 -------------------
 .../org/apache/juneau/rest/HtmlDocContext.java  | 171 -------
 .../java/org/apache/juneau/rest/RestConfig.java |  34 +-
 .../org/apache/juneau/rest/RestContext.java     |  29 +-
 .../org/apache/juneau/rest/RestRequest.java     | 123 +----
 .../apache/juneau/rest/annotation/HtmlDoc.java  |  32 +-
 .../org/apache/juneau/rest/vars/UrlVar.java     |   1 -
 .../org/apache/juneau/rest/vars/WidgetVar.java  |  18 +-
 .../org/apache/juneau/rest/widget/Widget.java   |   2 +-
 20 files changed, 573 insertions(+), 955 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/svl/ResolvingObjectMapTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/svl/ResolvingObjectMapTest.java
 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/svl/ResolvingObjectMapTest.java
new file mode 100644
index 0000000..17a1dee
--- /dev/null
+++ 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/svl/ResolvingObjectMapTest.java
@@ -0,0 +1,121 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                
                                              *
+// *                                                                           
                                              *
+// *  http://www.apache.org/licenses/LICENSE-2.0                               
                                              *
+// *                                                                           
                                              *
+// * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the 
License.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau.svl;
+
+import static org.junit.Assert.*;
+import static org.apache.juneau.TestUtils.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.utils.*;
+import org.junit.*;
+
+public class ResolvingObjectMapTest {
+       
+       
//====================================================================================================
+       // test - Basic tests
+       
//====================================================================================================
+       @Test
+       public void testBasic() throws Exception {
+               VarResolver vr = new 
VarResolverBuilder().defaultVars().vars(XVar.class).build();
+               ObjectMap m = new ResolvingObjectMap(vr.createSession());
+
+               m.put("foo", "$X{a}");
+               assertEquals(m.get("foo"), "1");
+
+               m.put("foo", new String[]{"$X{a}"});
+               assertObjectEquals("['1']", m.get("foo"));
+
+               m.put("foo", new AList<String>().append("$X{a}"));
+               assertObjectEquals("['1']", m.get("foo"));
+
+               m.put("foo", new AMap<String,String>().append("k1","$X{a}"));
+               assertObjectEquals("{k1:'1'}", m.get("foo"));
+       }
+       
+       public static class XVar extends MapVar {
+               public XVar() {
+                       super("X", new ObjectMap().append("a", 1).append("b", 
2).append("c", 3));
+               }
+       }
+
+       
//====================================================================================================
+       // testNulls
+       
//====================================================================================================
+       @Test
+       public void testNulls() throws Exception {
+               VarResolver vr = new 
VarResolverBuilder().defaultVars().vars(XVar.class).build();
+               ObjectMap m = new ResolvingObjectMap(vr.createSession());
+
+               m.put("foo", null);
+               assertNull(m.get("foo"));
+
+               m.put("foo", new String[]{null});
+               assertObjectEquals("[null]", m.get("foo"));
+
+               m.put("foo", new AList<String>().append(null));
+               assertObjectEquals("[null]", m.get("foo"));
+
+               m.put("foo", new AMap<String,String>().append("k1",null));
+               assertObjectEquals("{k1:null}", m.get("foo"));
+       }
+
+       
//====================================================================================================
+       // testNonStrings
+       
//====================================================================================================
+       @Test
+       public void testNonStrings() throws Exception {
+               VarResolver vr = new 
VarResolverBuilder().defaultVars().vars(XVar.class).build();
+               ObjectMap m = new ResolvingObjectMap(vr.createSession());
+
+               m.put("foo", FooEnum.ONE);
+               assertObjectEquals("'ONE'", m.get("foo"));
+
+               m.put("foo", new Object[]{FooEnum.ONE});
+               assertObjectEquals("['ONE']", m.get("foo"));
+
+               m.put("foo", new AList<FooEnum>().append(FooEnum.ONE));
+               assertObjectEquals("['ONE']", m.get("foo"));
+
+               m.put("foo", new 
AMap<FooEnum,FooEnum>().append(FooEnum.ONE,FooEnum.ONE));
+               assertObjectEquals("{ONE:'ONE'}", m.get("foo"));
+       }
+       
+       public static enum FooEnum {
+               ONE
+       }
+       
+       
//====================================================================================================
+       // testInner - Test inner maps
+       
//====================================================================================================
+       @Test
+       public void testInner() throws Exception {
+               VarResolver vr = new 
VarResolverBuilder().defaultVars().vars(XVar.class).build();
+               ObjectMap m = new ResolvingObjectMap(vr.createSession());
+               ObjectMap m2 = new ObjectMap();
+               ObjectMap m3 = new ObjectMap();
+               m.setInner(m2);
+               m2.setInner(m3);
+
+               m3.put("foo", "$X{a}");
+               assertEquals(m.get("foo"), "1");
+
+               m3.put("foo", new String[]{"$X{a}"});
+               assertObjectEquals("['1']", m.get("foo"));
+
+               m3.put("foo", new AList<String>().append("$X{a}"));
+               assertObjectEquals("['1']", m.get("foo"));
+
+               m3.put("foo", new AMap<String,String>().append("k1","$X{a}"));
+               assertObjectEquals("{k1:'1'}", m.get("foo"));
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectMap.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectMap.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectMap.java
index e3ab9bc..7eef2dd 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectMap.java
@@ -692,25 +692,16 @@ public class ObjectMap extends 
LinkedHashMap<String,Object> {
        }
 
        /**
-        * Specialized method that calls {@link #getString(String)} and splits 
the results as a simple comma-delimited list.
+        * Returns the specified entry value converted to a {@link String}.
         *
         * <p>
-        * If the value is already a collection, the individual entries are 
converted to strings using {@link #toString()}.
+        * Shortcut for <code>get(key, String[].<jk>class</jk>)</code>.
         *
-        * @param key the key.
-        * @return
-        *      A list of tokens, trimmed of whitespace.
-        *      An empty list if entry not found.
-        *      Never <jk>null</jk>.
+        * @param key The key.
+        * @return The converted value, or <jk>null</jk> if the map contains no 
mapping for this key.
         */
        public String[] getStringArray(String key) {
-               Object s = get(key, Object.class);
-               if (s == null)
-                       return new String[0];
-               if (s instanceof Collection)
-                       return ArrayUtils.toStringArray((Collection<?>)s);
-               String[] r = split(StringUtils.toString(s));
-               return r;
+               return getStringArray(key, null);
        }
 
        /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java
index 6296ff3..ef2b964 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java
@@ -85,7 +85,7 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *
         * <ul>
         *      <li><b>Name:</b> <js>"HtmlDocSerializer.header"</js>
-        *      <li><b>Data type:</b> <code>String</code>
+        *      <li><b>Data type:</b> <code>String[]</code>
         *      <li><b>Default:</b> <jk>null</jk>
         *      <li><b>Session-overridable:</b> <jk>true</jk>
         * </ul>
@@ -104,11 +104,8 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *              )
         *      )
         * </p>
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to represent no value to 
differentiate it from an empty string.
         */
-       public static final String HTMLDOC_header = PREFIX + "header";
+       public static final String HTMLDOC_header = PREFIX + "header.list";
 
        /**
         * <b>Configuration property:</b>  Page navigation links.
@@ -185,7 +182,7 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *
         * <ul>
         *      <li><b>Name:</b> <js>"HtmlDocSerializer.nav"</js>
-        *      <li><b>Data type:</b> <code>String</code>
+        *      <li><b>Data type:</b> <code>String[]</code>
         *      <li><b>Default:</b> <jk>null</jk>
         *      <li><b>Session-overridable:</b> <jk>true</jk>
         * </ul>
@@ -207,18 +204,15 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *
         * <p>
         * When this property is specified, the {@link #HTMLDOC_navlinks} 
property is ignored.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to represent no value to 
differentiate it from an empty string.
         */
-       public static final String HTMLDOC_nav = PREFIX + "nav";
+       public static final String HTMLDOC_nav = PREFIX + "nav.list";
 
        /**
         * <b>Configuration property:</b>  Aside section contents.
         *
         * <ul>
         *      <li><b>Name:</b> <js>"HtmlDocSerializer.aside"</js>
-        *      <li><b>Data type:</b> <code>String</code>
+        *      <li><b>Data type:</b> <code>String[]</code>
         *      <li><b>Default:</b> <jk>null</jk>
         *      <li><b>Session-overridable:</b> <jk>true</jk>
         * </ul>
@@ -245,18 +239,15 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *              )
         *      )
         * </p>
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to represent no value to 
differentiate it from an empty string.
         */
-       public static final String HTMLDOC_aside = PREFIX + "aside";
+       public static final String HTMLDOC_aside = PREFIX + "aside.list";
 
        /**
         * <b>Configuration property:</b>  Footer section contents.
         *
         * <ul>
         *      <li><b>Name:</b> <js>"HtmlDocSerializer.footer"</js>
-        *      <li><b>Data type:</b> <code>String</code>
+        *      <li><b>Data type:</b> <code>String[]</code>
         *      <li><b>Default:</b> <jk>null</jk>
         *      <li><b>Session-overridable:</b> <jk>true</jk>
         * </ul>
@@ -277,11 +268,8 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *              )
         *      )
         * </p>
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to represent no value to 
differentiate it from an empty string.
         */
-       public static final String HTMLDOC_footer = PREFIX + "footer";
+       public static final String HTMLDOC_footer = PREFIX + "footer.list";
 
        /**
         * <b>Configuration property:</b>  No-results message.
@@ -330,8 +318,8 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *
         * <ul>
         *      <li><b>Name:</b> <js>"HtmlDocSerializer.stylesheet"</js>
-        *      <li><b>Data type:</b> <code>List&lt;String&gt;</code>
-        *      <li><b>Default:</b> empty list
+        *      <li><b>Data type:</b> <code>String[]</code>
+        *      <li><b>Default:</b> empty array
         *      <li><b>Session-overridable:</b> <jk>true</jk>
         * </ul>
         *
@@ -340,11 +328,8 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *
         * <p>
         * Note that this stylesheet is controlled by the 
<code><ja>@RestResource</ja>.stylesheet()</code> annotation.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to represent no value to 
differentiate it from an empty string.
         */
-       public static final String HTMLDOC_stylesheet = PREFIX + "stylesheet";
+       public static final String HTMLDOC_stylesheet = PREFIX + 
"stylesheet.list";
 
        /**
         * <b>Configuration property:</b>  Add to the {@link 
#HTMLDOC_stylesheet} property.
@@ -356,8 +341,8 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *
         * <ul>
         *      <li><b>Name:</b> <js>"HtmlDocSerializer.style.list"</js>
-        *      <li><b>Data type:</b> <code>List&lt;String&gt;</code>
-        *      <li><b>Default:</b> empty list
+        *      <li><b>Data type:</b> <code>String[]</code>
+        *      <li><b>Default:</b> empty array
         *      <li><b>Session-overridable:</b> <jk>true</jk>
         * </ul>
         *
@@ -399,8 +384,8 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *
         * <ul>
         *      <li><b>Name:</b> <js>"HtmlDocSerializer.script.list"</js>
-        *      <li><b>Data type:</b> <code>List&lt;String&gt;</code>
-        *      <li><b>Default:</b> empty list
+        *      <li><b>Data type:</b> <code>String[]</code>
+        *      <li><b>Default:</b> empty array
         *      <li><b>Session-overridable:</b> <jk>true</jk>
         * </ul>
         *
@@ -441,8 +426,8 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
         *
         * <ul>
         *      <li><b>Name:</b> <js>"HtmlDocSerializer.head.list"</js>
-        *      <li><b>Data type:</b> <code>List&lt;String&gt;</code>
-        *      <li><b>Default:</b> empty list
+        *      <li><b>Data type:</b> <code>String[]</code>
+        *      <li><b>Default:</b> empty array
         *      <li><b>Session-overridable:</b> <jk>true</jk>
         * </ul>
         *
@@ -507,8 +492,8 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
        public static final String HTMLDOC_template = PREFIX + "template";
 
 
-       final String[] style, stylesheet, script, navlinks, head;
-       final String header, nav, aside, footer, noResultsMessage;
+       final String[] style, stylesheet, script, navlinks, head, header, nav, 
aside, footer;
+       final String noResultsMessage;
        final boolean nowrap;
        final HtmlDocTemplate template;
 
@@ -526,10 +511,10 @@ public final class HtmlDocSerializerContext extends 
HtmlSerializerContext {
                stylesheet = ps.getProperty(HTMLDOC_stylesheet, String[].class, 
new String[0]);
                script = ps.getProperty(HTMLDOC_script, String[].class, new 
String[0]);
                head = ps.getProperty(HTMLDOC_head, String[].class, new 
String[0]);
-               header = ps.getProperty(HTMLDOC_header, String.class, null);
-               nav = ps.getProperty(HTMLDOC_nav, String.class, null);
-               aside = ps.getProperty(HTMLDOC_aside, String.class, null);
-               footer = ps.getProperty(HTMLDOC_footer, String.class, null);
+               header = ps.getProperty(HTMLDOC_header, String[].class, new 
String[0]);
+               nav = ps.getProperty(HTMLDOC_nav, String[].class, new 
String[0]);
+               aside = ps.getProperty(HTMLDOC_aside, String[].class, new 
String[0]);
+               footer = ps.getProperty(HTMLDOC_footer, String[].class, new 
String[0]);
                nowrap = ps.getProperty(HTMLDOC_nowrap, boolean.class, false);
                navlinks = ps.getProperty(HTMLDOC_navlinks, String[].class, new 
String[0]);
                noResultsMessage = ps.getProperty(HTMLDOC_noResultsMessage, 
String.class, "<p>no results</p>");

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
index bd59d78..007c260 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
@@ -29,8 +29,8 @@ import org.apache.juneau.serializer.*;
  */
 public class HtmlDocSerializerSession extends HtmlStrippedDocSerializerSession 
{
 
-       private final String header, nav, aside, footer, noResultsMessage;
-       private final String[] style, stylesheet, script, navlinks, head;
+       private final String noResultsMessage;
+       private final String[] style, stylesheet, script, navlinks, head, 
header, nav, aside, footer;
        private final boolean nowrap;
        private final HtmlDocTemplate template;
 
@@ -47,10 +47,10 @@ public class HtmlDocSerializerSession extends 
HtmlStrippedDocSerializerSession {
                super(ctx, args);
                ObjectMap p = getProperties();
 
-               header = p.getString(HTMLDOC_header, ctx.nav);
-               nav = p.getString(HTMLDOC_nav, ctx.nav);
-               aside = p.getString(HTMLDOC_aside, ctx.aside);
-               footer = p.getString(HTMLDOC_footer, ctx.footer);
+               header = p.getStringArray(HTMLDOC_header, ctx.nav);
+               nav = p.getStringArray(HTMLDOC_nav, ctx.nav);
+               aside = p.getStringArray(HTMLDOC_aside, ctx.aside);
+               footer = p.getStringArray(HTMLDOC_footer, ctx.footer);
                navlinks = p.getStringArray(HTMLDOC_navlinks, ctx.navlinks);
                style = p.getStringArray(HTMLDOC_style, ctx.style);
                stylesheet = p.getStringArray(HTMLDOC_stylesheet, 
ctx.stylesheet);
@@ -145,7 +145,7 @@ public class HtmlDocSerializerSession extends 
HtmlStrippedDocSerializerSession {
         *      <jk>null</jk> if not specified.
         *       Never an empty string.
         */
-       public final String getHeader() {
+       public final String[] getHeader() {
                return header;
        }
 
@@ -180,7 +180,7 @@ public class HtmlDocSerializerSession extends 
HtmlStrippedDocSerializerSession {
         *      <jk>null</jk> if not specified.
         *      Never an empty string.
         */
-       public final String getNav() {
+       public final String[] getNav() {
                return nav;
        }
 
@@ -192,7 +192,7 @@ public class HtmlDocSerializerSession extends 
HtmlStrippedDocSerializerSession {
         *      <jk>null</jk> if not specified.
         *      Never an empty string.
         */
-       public final String getAside() {
+       public final String[] getAside() {
                return aside;
        }
 
@@ -204,7 +204,7 @@ public class HtmlDocSerializerSession extends 
HtmlStrippedDocSerializerSession {
         *      <jk>null</jk> if not specified.
         *      Never an empty string.
         */
-       public final String getFooter() {
+       public final String[] getFooter() {
                return footer;
        }
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java
index ffaabb5..86044b2 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java
@@ -24,10 +24,11 @@ public class HtmlDocTemplateBasic implements 
HtmlDocTemplate {
 
        @Override /* HtmlDocTemplate */
        public void head(HtmlDocSerializerSession session, HtmlWriter w, Object 
o) throws Exception {
-               
-               for (String h : session.getHead()) 
-                       w.appendln(2, h);
-               
+
+               String[] head = session.getHead();
+               for (int i = 0; i < head.length; i++)
+                       w.sIf(i > 0).appendln(2, head[i]);
+
                if (hasStyle(session)) {
                        w.sTag(2, "style").nl(2);
                        style(session, w, o);
@@ -44,24 +45,22 @@ public class HtmlDocTemplateBasic implements 
HtmlDocTemplate {
        public void style(HtmlDocSerializerSession session, HtmlWriter w, 
Object o) throws Exception {
 
                String[] stylesheet = session.getStylesheet();
-               if (! ArrayUtils.contains("NONE", stylesheet))
-                       for (String ss : stylesheet)
-                               w.append(3, "@import 
").q().append(session.resolveUri(ss)).q().appendln("; ");
+               for (int i = 0; i < stylesheet.length; i++)
+                       w.sIf(i > 0).append(3, "@import 
").q().append(session.resolveUri(stylesheet[i])).q().appendln(";");
 
                if (session.isNoWrap())
                        w.appendln(3, "div.data * {white-space:nowrap;} ");
 
-               if (session.getStyle() != null)
-                       for (String style : session.getStyle())
-                               w.append(3, style).appendln(" ");
+               String[] style = session.getStyle();
+               for (int i = 0; i < style.length; i++)
+                       w.sIf(i > 0 || stylesheet.length > 0).appendln(3, 
style[i]);
        }
 
        @Override /* HtmlDocTemplate */
        public void script(HtmlDocSerializerSession session, HtmlWriter w, 
Object o) throws Exception {
-
-               if (session.getScript() != null)
-                       for (String script : session.getScript())
-                               w.append(3, script);
+               String[] script = session.getScript();
+               for (int i = 0; i < script.length; i++)
+                       w.sIf(i > 0).appendln(3, script[i]);
        }
 
        @Override /* HtmlDocTemplate */
@@ -103,18 +102,18 @@ public class HtmlDocTemplateBasic implements 
HtmlDocTemplate {
        @Override /* HtmlDocTemplate */
        public void header(HtmlDocSerializerSession session, HtmlWriter w, 
Object o) throws Exception {
                // Write the title of the page.
-               String header = session.getHeader();
-               if (exists(header))
-                       w.append(3, header).nl(3);
+               String[] header = session.getHeader();
+               for (int i = 0; i < header.length; i++)
+                       w.sIf(i > 0).appendln(3, header[i]);
        }
 
 
        @Override /* HtmlDocTemplate */
        public void nav(HtmlDocSerializerSession session, HtmlWriter w, Object 
o) throws Exception {
-               String nav = session.getNav();
-               if (nav != null) {
-                       if (exists(nav))
-                               w.append(3, nav).nl(3);
+               String[] nav = session.getNav();
+               if (nav.length > 0) {
+                       for (int i = 0; i < nav.length; i++)
+                               w.sIf(i > 0).appendln(3, nav[i]);
                } else {
                        String[] links = session.getNavLinks();
                        if (links.length > 0) {
@@ -142,9 +141,9 @@ public class HtmlDocTemplateBasic implements 
HtmlDocTemplate {
 
        @Override /* HtmlDocTemplate */
        public void aside(HtmlDocSerializerSession session, HtmlWriter w, 
Object o) throws Exception {
-               String aside = session.getAside();
-               if (exists(aside))
-                       w.append(4, aside);
+               String[] aside = session.getAside();
+               for (int i = 0; i < aside.length; i++)
+                       w.sIf(i > 0).appendln(4, aside[i]);
        }
 
        @Override /* HtmlDocTemplate */
@@ -172,9 +171,9 @@ public class HtmlDocTemplateBasic implements 
HtmlDocTemplate {
 
        @Override /* HtmlDocTemplate */
        public void footer(HtmlDocSerializerSession session, HtmlWriter w, 
Object o) throws Exception {
-               String footer = session.getFooter();
-               if (exists(footer))
-                       w.append(3, footer).nl(3);
+               String[] footer = session.getFooter();
+               for (int i = 0; i < footer.length; i++)
+                       w.sIf(i > 0).appendln(3, footer[i]);
        }
 
        @Override /* HtmlDocTemplate */
@@ -189,22 +188,22 @@ public class HtmlDocTemplateBasic implements 
HtmlDocTemplate {
 
        @Override /* HtmlDocTemplate */
        public boolean hasHeader(HtmlDocSerializerSession session) {
-               return exists(session.getHeader());
+               return session.getHeader().length > 0;
        }
 
        @Override /* HtmlDocTemplate */
        public boolean hasNav(HtmlDocSerializerSession session) {
-               return exists(session.getNav()) || session.getNavLinks().length 
> 0;
+               return session.getNav().length > 0 || 
session.getNavLinks().length > 0;
        }
 
        @Override /* HtmlDocTemplate */
        public boolean hasAside(HtmlDocSerializerSession session) {
-               return exists(session.getAside());
+               return session.getAside().length > 0;
        }
 
        @Override /* HtmlDocTemplate */
        public boolean hasFooter(HtmlDocSerializerSession session) {
-               return exists(session.getFooter());
+               return session.getFooter().length > 0;
        }
 
        private static boolean exists(String s) {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerWriter.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerWriter.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerWriter.java
index 41e76d7..8595fe8 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerWriter.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerWriter.java
@@ -277,6 +277,23 @@ public class SerializerWriter extends Writer {
        }
 
        /**
+        * Writes a space if the boolean expression is <jk>true</jk> and {@code 
useWhitespace} is false.
+        * 
+        * <p>
+        * Intended for cases in XML where text should be separated by either a 
space or newline.
+        * This ensures the text is separated by a space if whitespace is 
disabled.
+        *
+        * @param b The boolean flag.
+        * @return This object (for method chaining).
+        * @throws IOException If a problem occurred trying to write to the 
writer.
+        */
+       public SerializerWriter sIf(boolean b) throws IOException {
+               if (b && ! useWhitespace)
+                       out.write(' ');
+               return this;
+       }
+
+       /**
         * Writes a newline to the writer if the {@code useWhitespace} setting 
is enabled and the boolean flag is true.
         *
         * @param b The boolean flag.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/ResolvingObjectMap.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/ResolvingObjectMap.java
 
b/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/ResolvingObjectMap.java
new file mode 100644
index 0000000..0890e52
--- /dev/null
+++ 
b/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/ResolvingObjectMap.java
@@ -0,0 +1,119 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                
                                              *
+// *                                                                           
                                              *
+// *  http://www.apache.org/licenses/LICENSE-2.0                               
                                              *
+// *                                                                           
                                              *
+// * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the 
License.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau.svl;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+
+/**
+ * Subclass of an {@link ObjectMap} that automatically resolves any SVL 
variables in values.
+ *
+ * <p>
+ * Resolves variables in the following values:
+ * <ul>
+ *     <li>Values of type {@link CharSequence}.
+ *     <li>Arrays containing values of type {@link CharSequence}.
+ *     <li>Collections containing values of type {@link CharSequence}.
+ *     <li>Maps containing values of type {@link CharSequence}.
+ * </ul>
+ *
+ * <p>
+ * All other data types are left as-is.
+ */
+@SuppressWarnings({"serial","unchecked","rawtypes"})
+public class ResolvingObjectMap extends ObjectMap {
+
+       private final VarResolverSession varResolver;
+
+       /**
+        * Constructor.
+        *
+        * @param varResolver The var resolver session to use for resolving SVL 
variables.
+        */
+       public ResolvingObjectMap(VarResolverSession varResolver) {
+               super();
+               this.varResolver = varResolver;
+       }
+
+       @Override /* Map */
+       public Object get(Object key) {
+               return resolve(super.get(key));
+       }
+
+       private Object resolve(Object o) {
+               if (o == null)
+                       return null;
+               if (o instanceof CharSequence)
+                       return varResolver.resolve(o.toString());
+               if (o.getClass().isArray()) {
+                       if (! containsVars(o))
+                               return o;
+                       Object o2 = 
Array.newInstance(o.getClass().getComponentType(), Array.getLength(o));
+                       for (int i = 0; i < Array.getLength(o); i++)
+                               Array.set(o2, i, resolve(Array.get(o, i)));
+                       return o2;
+               }
+               if (o instanceof Collection) {
+                       try {
+                               Collection c = (Collection)o;
+                               if (! containsVars(c))
+                                       return o;
+                               Collection c2 = c.getClass().newInstance();
+                               for (Object o2 : c)
+                                       c2.add(resolve(o2));
+                               return c2;
+                       } catch (Exception e) {
+                               return o;
+                       }
+               }
+               if (o instanceof Map) {
+                       try {
+                               Map m = (Map)o;
+                               if (! containsVars(m))
+                                       return o;
+                               Map m2 = m.getClass().newInstance();
+                               for (Map.Entry e : (Set<Map.Entry>)m.entrySet())
+                                       m2.put(e.getKey(), 
resolve(e.getValue()));
+                               return m2;
+                       } catch (Exception e) {
+                               return o;
+                       }
+               }
+               return o;
+       }
+
+       private static boolean containsVars(Object array) {
+               for (int i = 0; i < Array.getLength(array); i++) {
+                       Object o = Array.get(array, i);
+                       if (o instanceof CharSequence && 
o.toString().contains("$"))
+                               return true;
+               }
+               return false;
+       }
+
+       private static boolean containsVars(Collection c) {
+               for (Object o : c)
+                       if (o instanceof CharSequence && 
o.toString().contains("$"))
+                               return true;
+               return false;
+       }
+
+       private static boolean containsVars(Map m) {
+               for (Object o : m.values())
+                       if (o instanceof CharSequence && 
o.toString().contains("$"))
+                               return true;
+               return false;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/HtmlDocTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/HtmlDocTest.java
 
b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/HtmlDocTest.java
index d3c337b..976b83b 100644
--- 
a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/HtmlDocTest.java
+++ 
b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/HtmlDocTest.java
@@ -64,7 +64,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/test1");
                assertEquals("header1a header1b", header(r));
                assertEquals("script1a script1b", script(r));
-               assertEquals("@import '/testHtmlDoc/stylesheet1'; style1a 
style1b ", style(r));
+               assertEquals("@import '/testHtmlDoc/stylesheet1'; style1a 
style1b", style(r));
                assertEquals("nav1a nav1b", nav(r));
                assertEquals("aside1a aside1b", aside(r));
                assertEquals("footer1a footer1b", footer(r));
@@ -89,7 +89,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/test2");
                assertEquals("header2a header2b", header(r));
                assertEquals("script2a script2b", script(r));
-               assertEquals("@import '/testHtmlDoc/stylesheet2'; style2a 
style2b ", style(r));
+               assertEquals("@import '/testHtmlDoc/stylesheet2'; style2a 
style2b", style(r));
                assertEquals("nav2a nav2b", nav(r));
                assertEquals("aside2a aside2b", aside(r));
                assertEquals("footer2a footer2b", footer(r));
@@ -113,7 +113,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/test3");
                assertEquals("header1a header1b header3a header3b", header(r));
                assertEquals("script1a script1b script3a script3b", script(r));
-               assertEquals("@import '/testHtmlDoc/stylesheet1'; style1a 
style1b style3a style3b ", style(r));
+               assertEquals("@import '/testHtmlDoc/stylesheet1'; style1a 
style1b style3a style3b", style(r));
                assertEquals("nav1a nav1b nav3a nav3b", nav(r));
                assertEquals("aside1a aside1b aside3a aside3b", aside(r));
                assertEquals("footer1a footer1b footer3a footer3b", footer(r));
@@ -137,7 +137,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/test4");
                assertEquals("header4a header1a header1b header4b", header(r));
                assertEquals("script4a script1a script1b script4b", script(r));
-               assertEquals("@import '/testHtmlDoc/stylesheet1'; style4a 
style1a style1b style4b ", style(r));
+               assertEquals("@import '/testHtmlDoc/stylesheet1'; style4a 
style1a style1b style4b", style(r));
                assertEquals("nav4a nav1a nav1b nav4b", nav(r));
                assertEquals("aside4a aside1a aside1b aside4b", aside(r));
                assertEquals("footer4a footer1a footer1b footer4b", footer(r));
@@ -161,7 +161,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/test5");
                assertEquals("header5a header5b header1a header1b", header(r));
                assertEquals("script5a script5b script1a script1b", script(r));
-               assertEquals("@import '/testHtmlDoc/stylesheet1'; style5a 
style5b style1a style1b ", style(r));
+               assertEquals("@import '/testHtmlDoc/stylesheet1'; style5a 
style5b style1a style1b", style(r));
                assertEquals("nav5a nav5b nav1a nav1b", nav(r));
                assertEquals("aside5a aside5b aside1a aside1b", aside(r));
                assertEquals("footer5a footer5b footer1a footer1b", footer(r));
@@ -186,7 +186,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/testHtmlDoc2/test11");
                assertEquals("header11a header11b header1a header1b", 
header(r));
                assertEquals("script11a script11b", script(r));
-               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet11'; 
style11a style11b ", style(r));
+               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet11'; 
style11a style11b", style(r));
                assertEquals("nav1a nav1b nav11a nav11b", nav(r));
                assertEquals("aside1a aside1b aside11a aside11b", aside(r));
                assertEquals("footer11a footer1a footer1b footer11b", 
footer(r));
@@ -211,7 +211,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/testHtmlDoc2/test12");
                assertEquals("header12a header12b", header(r));
                assertEquals("script12a script12b", script(r));
-               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet12'; 
style12a style12b ", style(r));
+               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet12'; 
style12a style12b", style(r));
                assertEquals("nav12a nav12b", nav(r));
                assertEquals("aside12a aside12b", aside(r));
                assertEquals("footer12a footer12b", footer(r));
@@ -235,7 +235,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/testHtmlDoc2/test13");
                assertEquals("header11a header11b header1a header1b header13a 
header13b", header(r));
                assertEquals("script11a script11b script13a script13b", 
script(r));
-               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet11'; 
style11a style11b style13a style13b ", style(r));
+               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet11'; 
style11a style11b style13a style13b", style(r));
                assertEquals("nav1a nav1b nav11a nav11b nav13a nav13b", nav(r));
                assertEquals("aside1a aside1b aside11a aside11b aside13a 
aside13b", aside(r));
                assertEquals("footer11a footer1a footer1b footer11b footer13a 
footer13b", footer(r));
@@ -259,7 +259,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/testHtmlDoc2/test14");
                assertEquals("header14a header11a header11b header1a header1b 
header14b", header(r));
                assertEquals("script14a script11a script11b script14b", 
script(r));
-               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet11'; 
style14a style11a style11b style14b ", style(r));
+               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet11'; 
style14a style11a style11b style14b", style(r));
                assertEquals("nav14a nav1a nav1b nav11a nav11b nav14b", nav(r));
                assertEquals("aside14a aside1a aside1b aside11a aside11b 
aside14b", aside(r));
                assertEquals("footer14a footer11a footer1a footer1b footer11b 
footer14b", footer(r));
@@ -283,7 +283,7 @@ public class HtmlDocTest extends RestTestcase {
                String r = get("/testHtmlDoc/testHtmlDoc2/test15");
                assertEquals("header15a header15b header11a header11b header1a 
header1b", header(r));
                assertEquals("script15a script15b script11a script11b", 
script(r));
-               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet11'; 
style15a style15b style11a style11b ", style(r));
+               assertEquals("@import '/testHtmlDoc/testHtmlDoc2/stylesheet11'; 
style15a style15b style11a style11b", style(r));
                assertEquals("nav15a nav15b nav1a nav1b nav11a nav11b", nav(r));
                assertEquals("aside15a aside15b aside1a aside1b aside11a 
aside11b", aside(r));
                assertEquals("footer15a footer15b footer11a footer1a footer1b 
footer11b", footer(r));

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/PropertiesTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/PropertiesTest.java
 
b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/PropertiesTest.java
index f52a564..96cbfb4 100644
--- 
a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/PropertiesTest.java
+++ 
b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/PropertiesTest.java
@@ -30,14 +30,4 @@ public class PropertiesTest extends RestTestcase {
                String r = client.doGet(URL + 
"/testPropertiesDefinedOnMethod").getResponseAsString();
                
assertTrue(r.matches("A1=a1,A2=c,B1=b1,B2=c,C=c,R1a=.*/testProperties/testPropertiesDefinedOnMethod,R1b=.*/testProperties,R2=bar,R3=baz,R4=a1,R5=c,R6=c"));
        }
-
-       
//====================================================================================================
-       // Make sure attributes/parameters/headers are available through 
ctx.getProperties().
-       
//====================================================================================================
-       @Test
-       public void testProperties() throws Exception {
-               RestClient client = TestMicroservice.DEFAULT_CLIENT;
-               String r = client.doGet(URL + 
"/testProperties/a1?P=p1").header("H", "h1").getResponseAsString();
-               assertEquals("A=a1,P=p1,H=h1", r);
-       }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
index 55ec42c..7880746 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
@@ -28,9 +28,11 @@ import javax.servlet.http.*;
 import org.apache.juneau.*;
 import org.apache.juneau.dto.swagger.*;
 import org.apache.juneau.encoders.*;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.widget.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.svl.*;
 import org.apache.juneau.urlencoding.*;
@@ -63,7 +65,7 @@ class CallMethod implements Comparable<CallMethod>  {
        private final Response[] responses;
        private final RestContext context;
        private final BeanContext beanContext;
-       private final HtmlDocContext htmlDocContext;
+       private final Map<String,Widget> widgets;
 
        CallMethod(Object servlet, java.lang.reflect.Method method, RestContext 
context) throws RestServletException {
                Builder b = new Builder(servlet, method, context);
@@ -95,7 +97,7 @@ class CallMethod implements Comparable<CallMethod>  {
                this.priority = b.priority;
                this.parameters = b.parameters;
                this.responses = b.responses;
-               this.htmlDocContext = new HtmlDocContext(method, 
context.getHtmlDocContext());
+               this.widgets = Collections.unmodifiableMap(b.widgets);
        }
 
        private static class Builder  {
@@ -117,6 +119,7 @@ class CallMethod implements Comparable<CallMethod>  {
                private Integer priority;
                private org.apache.juneau.rest.annotation.Parameter[] 
parameters;
                private Response[] responses;
+               private Map<String,Widget> widgets;
 
                private Builder(Object servlet, java.lang.reflect.Method 
method, RestContext context) throws RestServletException {
                        String sig = method.getDeclaringClass().getName() + '.' 
+ method.getName();
@@ -145,7 +148,7 @@ class CallMethod implements Comparable<CallMethod>  {
                                urlEncodingParser = 
context.getUrlEncodingParser();
                                beanContext = context.getBeanContext();
                                encoders = context.getEncoders();
-                               properties = context.getProperties();
+                               properties = new 
ObjectMap().setInner(context.getProperties());
                                defaultCharset = context.getDefaultCharset();
                                String paramFormat = context.getParamFormat();
 
@@ -154,6 +157,19 @@ class CallMethod implements Comparable<CallMethod>  {
                                if (! m.paramFormat().isEmpty())
                                        paramFormat = 
context.getVarResolver().resolve(m.paramFormat());
 
+                               HtmlDocBuilder hdb = new 
HtmlDocBuilder(properties);
+
+                               HtmlDoc hd = m.htmldoc();
+                               hdb.process(hd);
+
+                               widgets = new 
HashMap<String,Widget>(context.getWidgets());
+                               for (Class<? extends Widget> wc : hd.widgets()) 
{
+                                       Widget w = 
ClassUtils.newInstance(Widget.class, wc);
+                                       widgets.put(w.getName(), w);
+                                       hdb.script("INHERIT", 
"$W{"+w.getName()+".script}");
+                                       hdb.style("INHERIT", 
"$W{"+w.getName()+".style}");
+                               }
+
                                List<Inherit> si = 
Arrays.asList(m.serializersInherit());
                                List<Inherit> pi = 
Arrays.asList(m.parsersInherit());
 
@@ -461,10 +477,6 @@ class CallMethod implements Comparable<CallMethod>  {
                return null;
        }
 
-       HtmlDocContext getHtmlDocContext() {
-               return htmlDocContext;
-       }
-
        /**
         * Returns the localized Swagger tags for this Java method.
         */
@@ -749,9 +761,10 @@ class CallMethod implements Comparable<CallMethod>  {
                        req.getPathMatch().put(pathPattern.getVars()[i], 
patternVals[i]);
                req.getPathMatch().setRemainder(remainder);
 
-               ObjectMap requestProperties = 
createRequestProperties(properties, req);
+               ObjectMap requestProperties = new 
ResolvingObjectMap(req.getVarResolverSession()).setInner(properties);
+
                req.init(method, requestProperties, defaultRequestHeaders, 
defaultQuery, defaultFormData, defaultCharset,
-                       serializers, parsers, urlEncodingParser, beanContext, 
encoders, htmlDocContext.widgets, htmlDocContext);
+                       serializers, parsers, urlEncodingParser, beanContext, 
encoders, widgets);
                res.init(requestProperties, defaultCharset, serializers, 
urlEncodingSerializer, encoders);
 
                // Class-level guards
@@ -828,38 +841,6 @@ class CallMethod implements Comparable<CallMethod>  {
                return SC_OK;
        }
 
-       /**
-        * This method creates all the request-time properties.
-        */
-       ObjectMap createRequestProperties(final ObjectMap methodProperties, 
final RestRequest req) {
-               @SuppressWarnings("serial")
-               ObjectMap m = new ObjectMap() {
-                       @Override /* Map */
-                       public Object get(Object key) {
-                               Object o = super.get(key);
-                               if (o == null) {
-                                       String k = key.toString();
-                                       int i = k.indexOf('.');
-                                       if (i != -1) {
-                                               String prefix = k.substring(0, 
i);
-                                               String remainder = 
k.substring(i+1);
-                                               Object v = 
req.resolveProperty(CallMethod.this, prefix, remainder);
-                                               if (v != null)
-                                                       return v;
-                                       }
-                                       o = req.getPathMatch().get(k);
-                                       if (o == null)
-                                               o = req.getHeader(k);
-                               }
-                               if (o instanceof String)
-                                       o = 
req.getVarResolverSession().resolve(o.toString());
-                               return o;
-                       }
-               };
-               m.setInner(methodProperties);
-               return m;
-       }
-
        @Override /* Object */
        public String toString() {
                return "SimpleMethod: name=" + httpMethod + ", path=" + 
pathPattern.getPatternString();

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocBuilder.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocBuilder.java
index 99c6a3b..2eab1ec 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocBuilder.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocBuilder.java
@@ -14,8 +14,12 @@ package org.apache.juneau.rest;
 
 import static org.apache.juneau.html.HtmlDocSerializerContext.*;
 
+import java.util.*;
+import java.util.regex.*;
+
 import org.apache.juneau.*;
 import org.apache.juneau.html.*;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.utils.*;
 
@@ -23,7 +27,15 @@ import org.apache.juneau.utils.*;
  * Programmatic interface for setting properties used by the HtmlDoc 
serializer.
  *
  * <p>
- * This class is instantiated by calling the {@link 
RestResponse#getHtmlDocBuilder()} method.
+ * Basically just a convenience wrapper around the servlet or method level 
properties for setting properties defined
+ * by the {@link HtmlDocSerializerContext} class.
+ *
+ * <p>
+ * This class is instantiated through the following methods.
+ * <ul>
+ *     <li>{@link RestConfig#getHtmlDocBuilder()} - Set values 
programmatically during servlet initialization.
+ *     <li>{@link RestResponse#getHtmlDocBuilder()} - Set values 
programmatically during a REST request.
+ * </ul>
  */
 public class HtmlDocBuilder {
 
@@ -33,6 +45,33 @@ public class HtmlDocBuilder {
                this.properties = properties;
        }
 
+       void process(HtmlDoc hd) {
+               if (hd.header().length > 0)
+                       header((Object[])hd.header());
+               if (hd.nav().length > 0)
+                       nav((Object[])hd.nav());
+               if (hd.aside().length > 0)
+                       aside((Object[])hd.aside());
+               if (hd.footer().length > 0)
+                       footer((Object[])hd.footer());
+               if (hd.style().length > 0)
+                       style((Object[])hd.style());
+               if (hd.script().length > 0)
+                       script((Object[])hd.script());
+               if (hd.navlinks().length > 0)
+                       navlinks((Object[])hd.navlinks());
+               if (hd.head().length > 0)
+                       head((Object[])hd.head());
+               if (hd.stylesheet().length > 0)
+                       stylesheet((Object[])hd.stylesheet());
+               if (! hd.noResultsMessage().isEmpty())
+                       noResultsMessage(hd.noResultsMessage());
+               if (hd.nowrap())
+                       nowrap(true);
+               if (hd.template() != HtmlDocTemplate.class)
+                       template(hd.template());
+       }
+
        /**
         * Sets the HTML header section contents.
         *
@@ -44,7 +83,8 @@ public class HtmlDocBuilder {
         * to be whatever you want.
         *
         * <p>
-        * A value of <js>"NONE"</js> can be used to force no header.
+        * A value of <js>"INHERIT"</js> means copy the values from the parent.
+        * <br>A value of <js>"NONE"</js> can be used to force no value.
         *
         * <p>
         * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>).
@@ -64,8 +104,8 @@ public class HtmlDocBuilder {
         *      </ul>
         * @return This object (for method chaining).
         */
-       public HtmlDocBuilder header(Object value) {
-               return set(HTMLDOC_header, value);
+       public HtmlDocBuilder header(Object...value) {
+               return set(HTMLDOC_header, resolveList(value, 
properties.getStringArray(HTMLDOC_header)));
        }
 
        /**
@@ -83,7 +123,8 @@ public class HtmlDocBuilder {
         * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
         *
         * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
+        * A value of <js>"INHERIT"</js> means copy the values from the parent.
+        * <br>A value of <js>"NONE"</js> can be used to force no value.
         *
         * <p>
         * This field can also use URIs of any support type in {@link 
UriResolver}.
@@ -102,7 +143,7 @@ public class HtmlDocBuilder {
         * @return This object (for method chaining).
         */
        public HtmlDocBuilder navlinks(Object...value) {
-               return set(HTMLDOC_navlinks, value);
+               return set(HTMLDOC_navlinks, resolveLinks(value, 
properties.getStringArray(HTMLDOC_navlinks)));
        }
 
        /**
@@ -125,7 +166,8 @@ public class HtmlDocBuilder {
         * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
         *
         * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
+        * A value of <js>"INHERIT"</js> means copy the values from the parent.
+        * <br>A value of <js>"NONE"</js> can be used to force no value.
         *
         * <p>
         * This is the programmatic equivalent to the {@link HtmlDoc#nav() 
@HtmlDoc.nav()} annotation.
@@ -141,8 +183,8 @@ public class HtmlDocBuilder {
         *      </ul>
         * @return This object (for method chaining).
         */
-       public HtmlDocBuilder nav(Object value) {
-               return set(HTMLDOC_nav, value);
+       public HtmlDocBuilder nav(Object...value) {
+               return set(HTMLDOC_nav, resolveList(value, 
properties.getStringArray(HTMLDOC_nav)));
        }
 
        /**
@@ -159,7 +201,8 @@ public class HtmlDocBuilder {
         * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
         *
         * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
+        * A value of <js>"INHERIT"</js> means copy the values from the parent.
+        * <br>A value of <js>"NONE"</js> can be used to force no value.
         *
         * <p>
         * This is the programmatic equivalent to the {@link HtmlDoc#aside() 
@HtmlDoc.aside()} annotation.
@@ -175,8 +218,8 @@ public class HtmlDocBuilder {
         *      </ul>
         * @return This object (for method chaining).
         */
-       public HtmlDocBuilder aside(Object value) {
-               return set(HTMLDOC_aside, value);
+       public HtmlDocBuilder aside(Object...value) {
+               return set(HTMLDOC_aside, resolveList(value, 
properties.getStringArray(HTMLDOC_aside)));
        }
 
        /**
@@ -193,7 +236,8 @@ public class HtmlDocBuilder {
         * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
         *
         * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
+        * A value of <js>"INHERIT"</js> means copy the values from the parent.
+        * <br>A value of <js>"NONE"</js> can be used to force no value.
         *
         * <p>
         * This is the programmatic equivalent to the {@link HtmlDoc#footer() 
@HtmlDoc.footer()} annotation.
@@ -209,8 +253,8 @@ public class HtmlDocBuilder {
         *      </ul>
         * @return This object (for method chaining).
         */
-       public HtmlDocBuilder footer(Object value) {
-               return set(HTMLDOC_footer, value);
+       public HtmlDocBuilder footer(Object...value) {
+               return set(HTMLDOC_footer, resolveList(value, 
properties.getStringArray(HTMLDOC_footer)));
        }
 
        /**
@@ -224,7 +268,8 @@ public class HtmlDocBuilder {
         * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
         *
         * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
+        * A value of <js>"INHERIT"</js> means copy the values from the parent.
+        * <br>A value of <js>"NONE"</js> can be used to force no value.
         *
         * <p>
         * This is the programmatic equivalent to the {@link HtmlDoc#style() 
@HtmlDoc.style()} annotation.
@@ -240,8 +285,8 @@ public class HtmlDocBuilder {
         *      </ul>
         * @return This object (for method chaining).
         */
-       public HtmlDocBuilder style(Object value) {
-               return set(HTMLDOC_style, value);
+       public HtmlDocBuilder style(Object...value) {
+               return set(HTMLDOC_style, resolveList(value, 
properties.getStringArray(HTMLDOC_style)));
        }
 
        /**
@@ -275,8 +320,8 @@ public class HtmlDocBuilder {
         *      </ul>
         * @return This object (for method chaining).
         */
-       public HtmlDocBuilder stylesheet(Object value) {
-               return set(HTMLDOC_stylesheet, value);
+       public HtmlDocBuilder stylesheet(Object...value) {
+               return set(HTMLDOC_stylesheet, resolveSet(value, 
properties.getStringArray(HTMLDOC_nav)));
        }
 
        /**
@@ -290,7 +335,8 @@ public class HtmlDocBuilder {
         * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
         *
         * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
+        * A value of <js>"INHERIT"</js> means copy the values from the parent.
+        * <br>A value of <js>"NONE"</js> can be used to force no value.
         *
         * <p>
         * This is the programmatic equivalent to the {@link HtmlDoc#script() 
@HtmlDoc.script()} annotation.
@@ -306,8 +352,8 @@ public class HtmlDocBuilder {
         *      </ul>
         * @return This object (for method chaining).
         */
-       public HtmlDocBuilder script(Object value) {
-               return set(HTMLDOC_script, value);
+       public HtmlDocBuilder script(Object...value) {
+               return set(HTMLDOC_script, resolveList(value, 
properties.getStringArray(HTMLDOC_script)));
        }
 
        /**
@@ -321,7 +367,8 @@ public class HtmlDocBuilder {
         * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
         *
         * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
+        * A value of <js>"INHERIT"</js> means copy the values from the parent.
+        * <br>A value of <js>"NONE"</js> can be used to force no value.
         *
         * <p>
         * This is the programmatic equivalent to the {@link HtmlDoc#head() 
@HtmlDoc.head()} annotation.
@@ -337,7 +384,7 @@ public class HtmlDocBuilder {
         * @return This object (for method chaining).
         */
        public HtmlDocBuilder head(Object...value) {
-               return set(HTMLDOC_head, value);
+               return set(HTMLDOC_head, resolveList(value, 
properties.getStringArray(HTMLDOC_head)));
        }
 
        /**
@@ -401,6 +448,60 @@ public class HtmlDocBuilder {
                return set(HTMLDOC_template, value);
        }
 
+       private static final Pattern INDEXED_LINK_PATTERN = 
Pattern.compile("(?s)(\\S*)\\[(\\d+)\\]\\:(.*)");
+
+       private static String[] resolveLinks(Object[] value, String[] prev) {
+               List<String> list = new ArrayList<String>();
+               for (Object v : value) {
+                       String s = StringUtils.toString(v);
+                       if ("INHERIT".equals(s)) {
+                               list.addAll(Arrays.asList(prev));
+                       } else if (s.indexOf('[') != -1 && 
INDEXED_LINK_PATTERN.matcher(s).matches()) {
+                                       Matcher lm = 
INDEXED_LINK_PATTERN.matcher(s);
+                                       lm.matches();
+                                       String key = lm.group(1);
+                                       int index = Math.min(list.size(), 
Integer.parseInt(lm.group(2)));
+                                       String remainder = lm.group(3);
+                                       list.add(index, key.isEmpty() ? 
remainder : key + ":" + remainder);
+                       } else {
+                               list.add(s);
+                       }
+               }
+               return list.toArray(new String[list.size()]);
+       }
+
+       private static String[] resolveSet(Object[] value, String[] prev) {
+               Set<String> set = new HashSet<>();
+               for (Object v : value) {
+                       String s = StringUtils.toString(v);
+                       if ("INHERIT".equals(s)) {
+                               if (prev != null)
+                                       set.addAll(Arrays.asList(prev));
+                       } else if ("NONE".equals(s)) {
+                               return new String[0];
+                       } else {
+                               set.add(s);
+                       }
+               }
+               return set.toArray(new String[set.size()]);
+       }
+
+       private static String[] resolveList(Object[] value, String[] prev) {
+               List<String> set = new LinkedList<>();
+               for (Object v : value) {
+                       String s = StringUtils.toString(v);
+                       if ("INHERIT".equals(s)) {
+                               if (prev != null)
+                                       set.addAll(Arrays.asList(prev));
+                       } else if ("NONE".equals(s)) {
+                               return new String[0];
+                       } else {
+                               set.add(s);
+                       }
+               }
+               return set.toArray(new String[set.size()]);
+       }
+
        private HtmlDocBuilder set(String key, Object value) {
                properties.put(key, value);
                return this;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocConfig.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocConfig.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocConfig.java
deleted file mode 100644
index 0397198..0000000
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocConfig.java
+++ /dev/null
@@ -1,456 +0,0 @@
-// 
***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
-// * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
-// * with the License.  You may obtain a copy of the License at                
                                              *
-// *                                                                           
                                              *
-// *  http://www.apache.org/licenses/LICENSE-2.0                               
                                              *
-// *                                                                           
                                              *
-// * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
-// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
-// * specific language governing permissions and limitations under the 
License.                                              *
-// 
***************************************************************************************************************************
-package org.apache.juneau.rest;
-
-import static org.apache.juneau.rest.RestUtils.*;
-
-import java.util.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.html.*;
-import org.apache.juneau.internal.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.widget.*;
-import org.apache.juneau.utils.*;
-
-/**
- * Programmatic interface for setting properties used by the HtmlDoc 
serializer.
- */
-public class HtmlDocConfig {
-
-       String header, nav, aside, footer, style, stylesheet, script, 
noResultsMessage;
-       String[] navlinks, head;
-       boolean nowrap;
-       Object template = HtmlDocTemplateBasic.class;
-       List<Class<? extends Widget>> widgets = new ArrayList<Class<? extends 
Widget>>();
-
-       HtmlDocConfig process(HtmlDoc hd) {
-               for (Class<? extends Widget> cw : hd.widgets())
-                       widget(cw);
-               header(resolveNewlineSeparatedAnnotation(hd.header(), header));
-               nav(resolveNewlineSeparatedAnnotation(hd.nav(), nav));
-               aside(resolveNewlineSeparatedAnnotation(hd.aside(), aside));
-               footer(resolveNewlineSeparatedAnnotation(hd.footer(), footer));
-               style(resolveNewlineSeparatedAnnotation(hd.style(), style));
-               script(resolveNewlineSeparatedAnnotation(hd.script(), script));
-               navlinks((Object[])resolveLinks(hd.navlinks(), navlinks));
-               head((Object[])resolveContent(hd.head(), head));
-
-               if (! hd.stylesheet().isEmpty())
-                       stylesheet(hd.stylesheet());
-               if (! hd.noResultsMessage().isEmpty())
-                       noResultsMessage(hd.noResultsMessage());
-               if (hd.nowrap())
-                       nowrap(true);
-               if (hd.template() != HtmlDocTemplate.class)
-                       template(hd.template());
-
-               return this;
-       }
-
-       /**
-        * Sets the HTML header section contents.
-        *
-        * <p>
-        * The format of this value is HTML.
-        *
-        * <p>
-        * The page header normally contains the title and description, but 
this value can be used to override the contents
-        * to be whatever you want.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to force no header.
-        *
-        * <p>
-        * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>).
-        * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#header() 
@HtmlDoc.header()} annotation.
-        *
-        * @param value
-        *      The HTML header section contents.
-        *      Object will be converted to a string using {@link 
Object#toString()}.
-        *      <p>
-        *      <ul class='doctree'>
-        *              <li class='info'>
-        *                      <b>Tip:</b>  Use {@link StringMessage} to 
generate value with delayed serialization so as not to
-        *                              waste string concatenation cycles on 
non-HTML views.
-        *      </ul>
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig header(Object value) {
-               header = StringUtils.toString(value);
-               return this;
-       }
-
-       /**
-        * Sets the links in the HTML nav section.
-        *
-        * <p>
-        * 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
-        * relative (to the servlet) or absolute URLs.
-        *
-        * <p>
-        * The page links are positioned immediately under the title and text.
-        *
-        * <p>
-        * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>).
-        * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
-        *
-        * <p>
-        * This field can also use URIs of any support type in {@link 
UriResolver}.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#navlinks() 
@HtmlDoc.navlinks()} annotation.
-        *
-        * @param value
-        *      The HTML nav section links links.
-        *      <p>
-        *      <ul class='doctree'>
-        *              <li class='info'>
-        *                      <b>Tip:</b>  Use {@link StringMessage} to 
generate value with delayed serialization so as not to
-        *                              waste string concatenation cycles on 
non-HTML views.
-        *      </ul>
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig navlinks(Object...value) {
-               navlinks = StringUtils.toStrings(value);
-               return this;
-       }
-
-       /**
-        * Sets the HTML nav section contents.
-        *
-        * <p>
-        * The format of this value is HTML.
-        *
-        * <p>
-        * The nav section of the page contains the links.
-        *
-        * <p>
-        * The format of this value is HTML.
-        *
-        * <p>
-        * When a value is specified, the {@link #navlinks(Object[])} value 
will be ignored.
-        *
-        * <p>
-        * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>).
-        * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#nav() 
@HtmlDoc.nav()} annotation.
-        *
-        * @param value
-        *      The HTML nav section contents.
-        *      Object will be converted to a string using {@link 
Object#toString()}.
-        *      <p>
-        *      <ul class='doctree'>
-        *              <li class='info'>
-        *                      <b>Tip:</b>  Use {@link StringMessage} to 
generate value with delayed serialization so as not to
-        *                              waste string concatenation cycles on 
non-HTML views.
-        *      </ul>
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig nav(Object value) {
-               this.nav = StringUtils.toString(value);
-               return this;
-       }
-
-       /**
-        * Sets the HTML aside section contents.
-        *
-        * <p>
-        * The format of this value is HTML.
-        *
-        * <p>
-        * The aside section typically floats on the right side of the page.
-        *
-        * <p>
-        * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>).
-        * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#aside() 
@HtmlDoc.aside()} annotation.
-        *
-        * @param value
-        *      The HTML aside section contents.
-        *      Object will be converted to a string using {@link 
Object#toString()}.
-        *      <p>
-        *      <ul class='doctree'>
-        *              <li class='info'>
-        *                      <b>Tip:</b>  Use {@link StringMessage} to 
generate value with delayed serialization so as not to waste
-        *                              string concatenation cycles on non-HTML 
views.
-        *      </ul>
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig aside(Object value) {
-               this.aside = StringUtils.toString(value);
-               return this;
-       }
-
-       /**
-        * Sets the HTML footer section contents.
-        *
-        * <p>
-        * The format of this value is HTML.
-        *
-        * <p>
-        * The footer section typically floats on the bottom of the page.
-        *
-        * <p>
-        * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>).
-        * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#footer() 
@HtmlDoc.footer()} annotation.
-        *
-        * @param value
-        *      The HTML footer section contents.
-        *      Object will be converted to a string using {@link 
Object#toString()}.
-        *      <p>
-        *      <ul class='doctree'>
-        *              <li class='info'>
-        *                      <b>Tip:</b>  Use {@link StringMessage} to 
generate value with delayed serialization so as not to
-        *                              waste string concatenation cycles on 
non-HTML views.
-        *      </ul>
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig footer(Object value) {
-               this.footer = StringUtils.toString(value);
-               return this;
-       }
-
-       /**
-        * Sets the HTML CSS style section contents.
-        *
-        * <p>
-        * The format of this value is CSS.
-        *
-        * <p>
-        * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>).
-        * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#style() 
@HtmlDoc.style()} annotation.
-        *
-        * @param value
-        *      The HTML CSS style section contents.
-        *      Object will be converted to a string using {@link 
Object#toString()}.
-        *      <p>
-        *      <ul class='doctree'>
-        *              <li class='info'>
-        *                      <b>Tip:</b>  Use {@link StringMessage} to 
generate value with delayed serialization so as not to
-        *                              waste string concatenation cycles on 
non-HTML views.
-        *      </ul>
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig style(Object value) {
-               this.style = StringUtils.toString(value);
-               return this;
-       }
-
-       /**
-        * Sets the CSS URL in the HTML CSS style section.
-        *
-        * <p>
-        * The format of this value is a comma-delimited list of URLs.
-        *
-        * <p>
-        * Specifies the URL to the stylesheet to add as a link in the style 
tag in the header.
-        *
-        * <p>
-        * The format of this value is CSS.
-        *
-        * <p>
-        * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>) and can use URL protocols defined
-        * by {@link UriResolver}.
-        * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link 
HtmlDoc#stylesheet() @HtmlDoc.stylesheet()} annotation.
-        *
-        * @param value
-        *      The CSS URL in the HTML CSS style section.
-        *      Object will be converted to a string using {@link 
Object#toString()}.
-        *      <p>
-        *      <ul class='doctree'>
-        *              <li class='info'>
-        *                      <b>Tip:</b>  Use {@link StringMessage} to 
generate value with delayed serialization so as not to
-        *                              waste string concatenation cycles on 
non-HTML views.
-        *      </ul>
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig stylesheet(Object value) {
-               this.stylesheet = StringUtils.toString(value);
-               return this;
-       }
-
-       /**
-        * Sets the HTML script section contents.
-        *
-        * <p>
-        * The format of this value is Javascript.
-        *
-        * <p>
-        * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>).
-        * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#script() 
@HtmlDoc.script()} annotation.
-        *
-        * @param value
-        *      The HTML script section contents.
-        *      Object will be converted to a string using {@link 
Object#toString()}.
-        *      <p>
-        *      <ul class='doctree'>
-        *              <li class='info'>
-        *                      <b>Tip:</b>  Use {@link StringMessage} to 
generate value with delayed serialization so as not to
-        *                              waste string concatenation cycles on 
non-HTML views.
-        *      </ul>
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig script(Object value) {
-               this.script = StringUtils.toString(value);
-               return this;
-       }
-
-       /**
-        * Sets the HTML head section contents.
-        *
-        * <p>
-        * The format of this value is HTML.
-        *
-        * <p>
-        * This field can contain variables (e.g. 
<js>"$L{my.localized.variable}"</js>).
-        * <br>See {@link RestContext#getVarResolver()} for the list of 
supported variables.
-        *
-        * <p>
-        * A value of <js>"NONE"</js> can be used to force no value.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#head() 
@HtmlDoc.head()} annotation.
-        *
-        * @param value
-        *      The HTML head section contents.
-        *      <p>
-        *      <ul class='doctree'>
-        *              <li class='info'>
-        *                      <b>Tip:</b>  Use {@link StringMessage} to 
generate value with delayed serialization so as not to
-        *                              waste string concatenation cycles on 
non-HTML views.
-        *      </ul>
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig head(Object...value) {
-               this.head = StringUtils.toStrings(value);
-               return this;
-       }
-
-       /**
-        * Shorthand method for forcing the rendered HTML content to be no-wrap.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#nowrap() 
@HtmlDoc.nowrap()} annotation.
-        *
-        * @param value The new nowrap setting.
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig nowrap(boolean value) {
-               this.nowrap = value;
-               return this;
-       }
-
-       /**
-        * Specifies the text to display when serializing an empty array or 
collection.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link 
HtmlDoc#noResultsMessage() @HtmlDoc.noResultsMessage()}
-        * annotation.
-        *
-        * @param value The text to display when serializing an empty array or 
collection.
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig noResultsMessage(Object value) {
-               this.noResultsMessage = StringUtils.toString(value);
-               return this;
-       }
-
-       /**
-        * Specifies the template class to use for rendering the HTML page.
-        *
-        * <p>
-        * By default, uses {@link HtmlDocTemplateBasic} to render the 
contents, although you can provide your own custom
-        * renderer or subclasses from the basic class to have full control 
over how the page is rendered.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#template() 
@HtmlDoc.template()} annotation.
-        *
-        * @param value The HTML page template to use to render the HTML page.
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig template(Class<? extends HtmlDocTemplate> value) {
-               this.template = value;
-               return this;
-       }
-
-       /**
-        * Specifies the template class to use for rendering the HTML page.
-        *
-        * <p>
-        * By default, uses {@link HtmlDocTemplateBasic} to render the 
contents, although you can provide your own custom
-        * renderer or subclasses from the basic class to have full control 
over how the page is rendered.
-        *
-        * <p>
-        * This is the programmatic equivalent to the {@link HtmlDoc#template() 
@HtmlDoc.template()} annotation.
-        *
-        * @param value The HTML page template to use to render the HTML page.
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig template(HtmlDocTemplate value) {
-               this.template = value;
-               return this;
-       }
-
-       /**
-        * Defines widgets that can be used in conjunction with string 
variables of the form <js>"$W{name}"</js>to quickly
-        * generate arbitrary replacement text.
-        *
-        * <p>
-        * Widgets are inherited from parent to child, but can be overridden by 
reusing the widget name.
-        *
-        * @param value The widget class to add.
-        * @return This object (for method chaining).
-        */
-       public HtmlDocConfig widget(Class<? extends Widget> value) {
-               this.widgets.add(value);
-               return this;
-       }
-}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocContext.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocContext.java
deleted file mode 100644
index 9f3809e..0000000
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/HtmlDocContext.java
+++ /dev/null
@@ -1,171 +0,0 @@
-// 
***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
-// * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
-// * with the License.  You may obtain a copy of the License at                
                                              *
-// *                                                                           
                                              *
-// *  http://www.apache.org/licenses/LICENSE-2.0                               
                                              *
-// *                                                                           
                                              *
-// * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
-// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
-// * specific language governing permissions and limitations under the 
License.                                              *
-// 
***************************************************************************************************************************
-package org.apache.juneau.rest;
-
-import static org.apache.juneau.rest.RestUtils.*;
-
-import java.util.*;
-
-import org.apache.juneau.html.*;
-import org.apache.juneau.internal.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.widget.*;
-
-/**
- * Programmatic interface for set properties used by the HtmlDoc serializer.
- */
-public final class HtmlDocContext {
-
-       final String header, nav, aside, style, stylesheet, script, footer, 
noResultsMessage;
-       final String[] navlinks, head;
-       final boolean nowrap;
-       final HtmlDocTemplate template;
-       final Map<String,Widget> widgets;
-
-       HtmlDocContext(Object resource, RestConfig config) throws 
RestServletException {
-               try {
-                       Builder b = new Builder(resource, config);
-
-                       this.header = b.header;
-                       this.nav = b.nav;
-                       this.aside = b.aside;
-                       this.style = b.style;
-                       this.stylesheet = b.stylesheet;
-                       this.script = b.script;
-                       this.footer = b.footer;
-                       this.noResultsMessage = b.noResultsMessage;
-                       this.navlinks = b.navlinks;
-                       this.head = b.head;
-                       this.nowrap = b.nowrap;
-                       this.widgets = Collections.unmodifiableMap(b.widgets);
-                       this.template = b.template;
-               } catch (RestServletException e) {
-                       throw e;
-               } catch (Exception e) {
-                       throw new RestServletException("Exception occurred 
while initializing resource ''{0}''", 
resource.getClass().getSimpleName()).initCause(e);
-               }
-       }
-
-       HtmlDocContext(java.lang.reflect.Method method, HtmlDocContext pc) 
throws RestServletException {
-               try {
-                       Builder b = new Builder(method, pc);
-                       this.header = b.header;
-                       this.nav = b.nav;
-                       this.aside = b.aside;
-                       this.style = b.style;
-                       this.stylesheet = b.stylesheet;
-                       this.script = b.script;
-                       this.footer = b.footer;
-                       this.noResultsMessage = b.noResultsMessage;
-                       this.navlinks = b.navlinks;
-                       this.head = b.head;
-                       this.nowrap = b.nowrap;
-                       this.widgets = Collections.unmodifiableMap(b.widgets);
-                       this.template = b.template;
-               } catch (RestServletException e) {
-                       throw e;
-               } catch (Exception e) {
-                       String sig = method.getDeclaringClass().getName() + '.' 
+ method.getName();
-                       throw new RestServletException("Exception occurred 
while initializing method ''{0}''", sig).initCause(e);
-               }
-       }
-
-
-       static class Builder {
-
-               String header, nav, aside, style, stylesheet, script, footer, 
noResultsMessage;
-               String[] navlinks, head;
-               boolean nowrap;
-               HtmlDocTemplate template;
-               Map<String,Widget> widgets;
-
-
-               Builder(java.lang.reflect.Method method, HtmlDocContext pc) 
throws Exception {
-                       String sig = method.getDeclaringClass().getName() + '.' 
+ method.getName();
-
-                       try {
-                               RestMethod m = 
method.getAnnotation(RestMethod.class);
-                               if (m == null)
-                                       throw new 
RestServletException("@RestMethod annotation not found on method ''{0}''", sig);
-
-                                       HtmlDoc hd = m.htmldoc();
-
-                                       widgets = new 
HashMap<String,Widget>(pc.widgets);
-                                       for (Class<? extends Widget> wc : 
hd.widgets()) {
-                                               Widget w = 
ClassUtils.newInstance(Widget.class, wc);
-                                               widgets.put(w.getName(), w);
-                                       }
-
-                                       header = 
resolveNewlineSeparatedAnnotation(hd.header(), pc.header);
-                                       nav = 
resolveNewlineSeparatedAnnotation(hd.nav(), pc.nav);
-                                       aside = 
resolveNewlineSeparatedAnnotation(hd.aside(), pc.aside);
-                                       footer = 
resolveNewlineSeparatedAnnotation(hd.footer(), pc.footer);
-                                       style = 
resolveNewlineSeparatedAnnotation(hd.style(), pc.style);
-                                       script = 
resolveNewlineSeparatedAnnotation(hd.script(), pc.script);
-                                       head = resolveContent(hd.head(), 
pc.head);
-                                       navlinks = resolveLinks(hd.navlinks(), 
pc.navlinks);
-                                       stylesheet = hd.stylesheet().isEmpty() 
? pc.stylesheet : hd.stylesheet();
-                                       nowrap = hd.nowrap() ? hd.nowrap() : 
pc.nowrap;
-                                       noResultsMessage = 
hd.noResultsMessage().isEmpty() ? pc.noResultsMessage : hd.noResultsMessage();
-                                       template =
-                                               hd.template() == 
HtmlDocTemplate.class
-                                               ? pc.template
-                                               : 
ClassUtils.newInstance(HtmlDocTemplate.class, hd.template());
-                       } catch (RestServletException e) {
-                               throw e;
-                       } catch (Exception e) {
-                               throw new RestServletException("Exception 
occurred while initializing method ''{0}''", sig).initCause(e);
-                       }
-               }
-
-
-               Builder(Object resource, RestConfig sc) throws Exception {
-
-                       HtmlDocConfig hdc = sc.htmlDocConfig;
-
-                       this.widgets = new LinkedHashMap<String,Widget>();
-                       for (Class<? extends Widget> wc : hdc.widgets) {
-                               Widget w = resolve(resource, Widget.class, wc);
-                               this.widgets.put(w.getName(), w);
-                       }
-
-                       header = hdc.header;
-                       navlinks = hdc.navlinks;
-                       nav = hdc.nav;
-                       aside = hdc.aside;
-                       style = hdc.style;
-                       stylesheet = hdc.stylesheet;
-                       script = hdc.script;
-                       head = hdc.head;
-                       footer = hdc.footer;
-                       nowrap = hdc.nowrap;
-                       noResultsMessage = hdc.noResultsMessage;
-                       template = resolve(resource, HtmlDocTemplate.class, 
hdc.template);
-               }
-       }
-
-       
//----------------------------------------------------------------------------------------------------
-       // Utility methods
-       
//----------------------------------------------------------------------------------------------------
-
-       /**
-        * Takes in an object of type T or a Class<T> and either casts or 
constructs a T.
-        */
-       private static <T> T resolve(Object outer, Class<T> c, Object o, 
Object...cArgs) throws RestServletException {
-               try {
-                       return ClassUtils.newInstanceFromOuter(outer, c, o, 
cArgs);
-               } catch (Exception e) {
-                       throw new RestServletException("Exception occurred 
while constructing class ''{0}''", c).initCause(e);
-               }
-       }
-}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConfig.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConfig.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConfig.java
index ca8e558..3255041 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConfig.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConfig.java
@@ -33,6 +33,7 @@ import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.response.*;
 import org.apache.juneau.rest.vars.*;
+import org.apache.juneau.rest.widget.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.svl.*;
 import org.apache.juneau.svl.vars.*;
@@ -113,7 +114,8 @@ public class RestConfig implements ServletConfig {
        String path;
        String clientVersionHeader = "X-Client-Version";
        String contextPath;
-       HtmlDocConfig htmlDocConfig = new HtmlDocConfig();
+       HtmlDocBuilder htmlDocBuilder;
+       List<Class<? extends Widget>> widgets = new ArrayList<Class<? extends 
Widget>>();
 
        Object resourceResolver = RestResourceResolverSimple.class;
        Object logger = RestLogger.Normal.class;
@@ -156,6 +158,7 @@ public class RestConfig implements ServletConfig {
                        ConfigFileBuilder cfb = new ConfigFileBuilder();
 
                        properties = new ObjectMap();
+                       htmlDocBuilder = new HtmlDocBuilder(properties);
                        configFile = cfb.build();
                        varResolverBuilder = new VarResolverBuilder()
                                .vars(
@@ -245,7 +248,11 @@ public class RestConfig implements ServletConfig {
                                if (! r.paramFormat().isEmpty())
                                        
setParamFormat(vr.resolve(r.paramFormat()));
 
-                               htmlDocConfig.process(r.htmldoc());
+                               HtmlDoc hd = r.htmldoc();
+                               for (Class<? extends Widget> cw : hd.widgets())
+                                       this.widgets.add(cw);
+
+                               htmlDocBuilder.process(hd);
                        }
 
                        addResponseHandlers(
@@ -1176,12 +1183,27 @@ public class RestConfig implements ServletConfig {
        }
 
        /**
-        * Returns the configuration settings specific to the HTML doc view.
+        * Defines widgets that can be used in conjunction with string 
variables of the form <js>"$W{name}"</js>to quickly
+        * generate arbitrary replacement text.
+        *
+        * <p>
+        * Widgets are inherited from parent to child, but can be overridden by 
reusing the widget name.
         *
-        * @return The configuration settings specific to the HTML doc view.
+        * @param value The widget class to add.
+        * @return This object (for method chaining).
+        */
+       public RestConfig addWidget(Class<? extends Widget> value) {
+               this.widgets.add(value);
+               return this;
+       }
+       
+       /**
+        * Returns an instance of an HTMLDOC builder for setting 
HTMLDOC-related properties.
+        * 
+        * @return An instance of an HTMLDOC builder for setting 
HTMLDOC-related properties.
         */
-       public HtmlDocConfig getHtmlDocConfig() {
-               return htmlDocConfig;
+       public HtmlDocBuilder getHtmlDocBuilder() {
+               return htmlDocBuilder;
        }
 
        /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9c746e64/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index ef9200b..5515e48 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -41,6 +41,7 @@ import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.annotation.Properties;
 import org.apache.juneau.rest.vars.*;
+import org.apache.juneau.rest.widget.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.svl.*;
 import org.apache.juneau.svl.vars.*;
@@ -69,7 +70,7 @@ public final class RestContext extends Context {
                fullPath,
                contextPath;
 
-       private final HtmlDocContext htmlDocContext;
+       private final Map<String,Widget> widgets;
 
        private final Set<String> allowMethodParams;
 
@@ -190,7 +191,7 @@ public final class RestContext extends Context {
                        this.logger = b.logger;
                        this.fullPath = b.fullPath;
                        this.contextPath = nullIfEmpty(b.contextPath);
-                       this.htmlDocContext = new HtmlDocContext(resource, 
config);
+                       this.widgets = Collections.unmodifiableMap(b.widgets);
 
                        
//----------------------------------------------------------------------------------------------------
                        // Initialize the child resources.
@@ -471,6 +472,7 @@ public final class RestContext extends Context {
                Set<String> allowMethodParams = new LinkedHashSet<String>();
                RestLogger logger;
                String fullPath;
+               Map<String,Widget> widgets;
                Object resourceResolver;
                String contextPath;
 
@@ -573,6 +575,18 @@ public final class RestContext extends Context {
                        logger = sc.logger == null ? new RestLogger.NoOp() : 
resolve(resource, RestLogger.class, sc.logger);
 
                        fullPath = (sc.parentContext == null ? "" : 
(sc.parentContext.fullPath + '/')) + sc.path;
+
+                       HtmlDocBuilder hdb = new HtmlDocBuilder(sc.properties);
+
+                       this.widgets = new LinkedHashMap<String,Widget>();
+
+                       for (Class<? extends Widget> wc : sc.widgets) {
+                               Widget w = resolve(resource, Widget.class, wc);
+                               String n = w.getName();
+                               this.widgets.put(n, w);
+                               hdb.script("INHERIT", "$W{"+n+".script}");
+                               hdb.style("INHERIT", "$W{"+n+".style}");
+                       }
                }
        }
 
@@ -838,12 +852,15 @@ public final class RestContext extends Context {
        }
 
        /**
-        * Returns the context values for the HTML doc view.
+        * The widgets used for resolving <js>"$W{...}"<js> variables.
+        *
+        * <p>
+        * Defined by the {@link HtmlDoc#widgets()} annotation or {@link 
RestConfig#addWidget(Class)} method.
         *
-        * @return The context values for the HTML doc view.
+        * @return The var resolver widgets as a map with keys being the name 
returned by {@link Widget#getName()}.
         */
-       public HtmlDocContext getHtmlDocContext() {
-               return htmlDocContext;
+       public Map<String,Widget> getWidgets() {
+               return widgets;
        }
 
        /**


Reply via email to