You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/10/09 22:57:52 UTC

[2/2] incubator-juneau git commit: Clean up how HtmlDoc properties are defined.

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 <ja...@apache.org>
Authored: Mon Oct 9 18:57:45 2017 -0400
Committer: JamesBognar <ja...@apache.org>
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;
 	}
 
 	/**