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/05/29 22:21:59 UTC

[2/5] incubator-juneau git commit: Improved support for resolution of URIs.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerBuilder.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerBuilder.java
index fe9858b..79d6a29 100644
--- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerBuilder.java
@@ -378,97 +378,111 @@ public class SerializerBuilder extends CoreObjectBuilder {
 	}
 
 	/**
-	 * <b>Configuration property:</b>  URI base for relative URIs.
+	 * <b>Configuration property:</b>  URI context bean.
 	 * <p>
 	 * <ul>
-	 * 	<li><b>Name:</b> <js>"Serializer.relativeUriBase"</js>
-	 * 	<li><b>Data type:</b> <code>String</code>
-	 * 	<li><b>Default:</b> <js>""</js>
+	 * 	<li><b>Name:</b> <js>"Serializer.uriContext"</js>
+	 * 	<li><b>Data type:</b> {@link UriContext}
+	 * 	<li><b>Default:</b> {@link UriContext#DEFAULT}
 	 * 	<li><b>Session-overridable:</b> <jk>true</jk>
 	 * </ul>
 	 * <p>
-	 * Prepended to relative URIs during serialization (along with the {@link SerializerContext#SERIALIZER_absolutePathUriBase} if specified.
-	 * (i.e. URIs not containing a schema and not starting with <js>'/'</js>).
-	 * (e.g. <js>"foo/bar"</js>)
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <table class='styled'>
-	 * 	<tr><th>SERIALIZER_relativeUriBase</th><th>URI</th><th>Serialized URI</th></tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>mywebapp</code></td>
-	 * 		<td><code>http://foo:9080/bar/baz/mywebapp</code></td>
-	 * 	</tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>/mywebapp</code></td>
-	 * 		<td><code>/mywebapp</code></td>
-	 * 	</tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>http://mywebapp</code></td>
-	 * 		<td><code>http://mywebapp</code></td>
-	 * 	</tr>
-	 * </table>
+	 * Bean used for resolution of URIs to absolute or root-relative form.
+	 * <p>
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>
+	 * </p>
 	 * <p>
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul>
-	 * 	<li>This is equivalent to calling <code>property(<jsf>SERIALIZER_relativeUriBase</jsf>, value)</code>.
+	 * 	<li>This is equivalent to calling <code>property(<jsf>SERIALIZER_uriContext</jsf>, value)</code>.
 	 * </ul>
 	 *
 	 * @param value The new value for this property.
 	 * @return This object (for method chaining).
-	 * @see SerializerContext#SERIALIZER_relativeUriBase
+	 * @see SerializerContext#SERIALIZER_uriContext
 	 */
-	public SerializerBuilder relativeUriBase(String value) {
-		return property(SERIALIZER_relativeUriBase, value);
+	public SerializerBuilder uriContext(UriContext value) {
+		return property(SERIALIZER_uriContext, value);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  URI base for relative URIs with absolute paths.
+	 * <b>Configuration property:</b>  URI resolution.
 	 * <p>
 	 * <ul>
-	 * 	<li><b>Name:</b> <js>"Serializer.absolutePathUriBase"</js>
-	 * 	<li><b>Data type:</b> <code>String</code>
-	 * 	<li><b>Default:</b> <js>""</js>
+	 * 	<li><b>Name:</b> <js>"Serializer.uriResolution"</js>
+	 * 	<li><b>Data type:</b> {@link UriResolution}
+	 * 	<li><b>Default:</b> {@link UriResolution#ROOT_RELATIVE}
 	 * 	<li><b>Session-overridable:</b> <jk>true</jk>
 	 * </ul>
 	 * <p>
-	 * Prepended to relative absolute-path URIs during serialization.
-	 * (i.e. URIs starting with <js>'/'</js>).
-	 * (e.g. <js>"/foo/bar"</js>)
+	 * Defines the resolution level for URIs when serializing any of the following:
+	 * <ul>
+	 * 	<li>{@link java.net.URI}
+	 * 	<li>{@link java.net.URL}
+	 * 	<li>Properties annotated with {@link org.apache.juneau.annotation.URI @URI}
+	 * </ul>
+	 * <p>
+	 * Possible values are:
+	 * <ul>
+	 * 	<li>{@link UriResolution#ABSOLUTE}
+	 * 		- Resolve to an absolute URL (e.g. <js>"http://host:port/context-root/servlet-path/path-info"</js>).
+	 * 	<li>{@link UriResolution#ROOT_RELATIVE}
+	 * 		- Resolve to a root-relative URL (e.g. <js>"/context-root/servlet-path/path-info"</js>).
+	 * 	<li>{@link UriResolution#NONE}
+	 * 		- Don't do any URL resolution.
+	 * </ul>
+	 * <p>
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul>
+	 * 	<li>This is equivalent to calling <code>property(<jsf>SERIALIZER_uriResolution</jsf>, value)</code>.
+	 * </ul>
 	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <table class='styled'>
-	 * 	<tr><th>SERIALIZER_absolutePathUriBase</th><th>URI</th><th>Serialized URI</th></tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>mywebapp</code></td>
-	 * 		<td><code>mywebapp</code></td>
-	 * 	</tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>/mywebapp</code></td>
-	 * 		<td><code>http://foo:9080/bar/baz/mywebapp</code></td>
-	 * 	</tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>http://mywebapp</code></td>
-	 * 		<td><code>http://mywebapp</code></td>
-	 * 	</tr>
-	 * </table>
+	 * @param value The new value for this property.
+	 * @return This object (for method chaining).
+	 * @see SerializerContext#SERIALIZER_uriResolution
+	 */
+	public SerializerBuilder uriResolution(UriResolution value) {
+		return property(SERIALIZER_uriResolution, value);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  URI relativity.
+	 * <p>
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"Serializer.uriRelativity"</js>
+	 * 	<li><b>Data type:</b> {@link UriRelativity}
+	 * 	<li><b>Default:</b> {@link UriRelativity#RESOURCE}
+	 * 	<li><b>Session-overridable:</b> <jk>true</jk>
+	 * </ul>
+	 * <p>
+	 * Defines what relative URIs are relative to when serializing any of the following:
+	 * <ul>
+	 * 	<li>{@link java.net.URI}
+	 * 	<li>{@link java.net.URL}
+	 * 	<li>Properties annotated with {@link org.apache.juneau.annotation.URI @URI}
+	 * </ul>
+	 * <p>
+	 * Possible values are:
+	 * <ul>
+	 * 	<li>{@link UriRelativity#RESOURCE}
+	 * 		- Relative URIs should be considered relative to the servlet URI.
+	 * 	<li>{@link UriRelativity#PATH_INFO}
+	 * 		- Relative URIs should be considered relative to the request URI.
+	 * </ul>
 	 * <p>
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul>
-	 * 	<li>This is equivalent to calling <code>property(<jsf>SERIALIZER_absolutePathUriBase</jsf>, value)</code>.
+	 * 	<li>This is equivalent to calling <code>property(<jsf>SERIALIZER_uriRelativity</jsf>, value)</code>.
 	 * </ul>
 	 *
 	 * @param value The new value for this property.
 	 * @return This object (for method chaining).
-	 * @see SerializerContext#SERIALIZER_absolutePathUriBase
+	 * @see SerializerContext#SERIALIZER_uriRelativity
 	 */
-	public SerializerBuilder absolutePathUriBase(String value) {
-		return property(SERIALIZER_absolutePathUriBase, value);
+	public SerializerBuilder uriRelativity(UriRelativity value) {
+		return property(SERIALIZER_uriRelativity, value);
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java
index fd890f9..372e8e0 100644
--- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java
+++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java
@@ -14,7 +14,6 @@ package org.apache.juneau.serializer;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.internal.*;
 
 /**
  * Configurable properties common to all serializers.
@@ -215,96 +214,79 @@ public class SerializerContext extends BeanContext {
 	public static final String SERIALIZER_trimStrings = "Serializer.trimStrings";
 
 	/**
-	 * <b>Configuration property:</b>  URI base for relative URIs.
+	 * <b>Configuration property:</b>  URI context bean.
 	 * <p>
 	 * <ul>
-	 * 	<li><b>Name:</b> <js>"Serializer.relativeUriBase"</js>
-	 * 	<li><b>Data type:</b> <code>String</code>
-	 * 	<li><b>Default:</b> <js>""</js>
+	 * 	<li><b>Name:</b> <js>"Serializer.uriContext"</js>
+	 * 	<li><b>Data type:</b> {@link UriContext}
+	 * 	<li><b>Default:</b> {@link UriContext#DEFAULT}
 	 * 	<li><b>Session-overridable:</b> <jk>true</jk>
 	 * </ul>
 	 * <p>
-	 * Prepended to relative URIs during serialization (along with the {@link #SERIALIZER_absolutePathUriBase} if specified.
-	 * (i.e. URIs not containing a schema and not starting with <js>'/'</js>).
-	 * (e.g. <js>"foo/bar"</js>)
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <table class='styled'>
-	 * 	<tr><th>SERIALIZER_relativeUriBase</th><th>URI</th><th>Serialized URI</th></tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>mywebapp</code></td>
-	 * 		<td><code>http://foo:9080/bar/baz/mywebapp</code></td>
-	 * 	</tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>/mywebapp</code></td>
-	 * 		<td><code>/mywebapp</code></td>
-	 * 	</tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>http://mywebapp</code></td>
-	 * 		<td><code>http://mywebapp</code></td>
-	 * 	</tr>
-	 * </table>
+	 * Bean used for resolution of URIs to absolute or root-relative form.
+	 * <p>
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>
+	 * </p>
 	 */
-	public static final String SERIALIZER_relativeUriBase = "Serializer.relativeUriBase";
+	public static final String SERIALIZER_uriContext = "Serializer.uriContext";
 
 	/**
-	 * <b>Configuration property:</b>  URI base for relative URIs with absolute paths.
+	 * <b>Configuration property:</b>  URI resolution.
 	 * <p>
 	 * <ul>
-	 * 	<li><b>Name:</b> <js>"Serializer.absolutePathUriBase"</js>
-	 * 	<li><b>Data type:</b> <code>String</code>
-	 * 	<li><b>Default:</b> <js>""</js>
+	 * 	<li><b>Name:</b> <js>"Serializer.uriResolution"</js>
+	 * 	<li><b>Data type:</b> {@link UriResolution}
+	 * 	<li><b>Default:</b> {@link UriResolution#ROOT_RELATIVE}
 	 * 	<li><b>Session-overridable:</b> <jk>true</jk>
 	 * </ul>
 	 * <p>
-	 * Prepended to relative absolute-path URIs during serialization.
-	 * (i.e. URIs starting with <js>'/'</js>).
-	 * (e.g. <js>"/foo/bar"</js>)
-	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <table class='styled'>
-	 * 	<tr><th>SERIALIZER_absolutePathUriBase</th><th>URI</th><th>Serialized URI</th></tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>mywebapp</code></td>
-	 * 		<td><code>mywebapp</code></td>
-	 * 	</tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>/mywebapp</code></td>
-	 * 		<td><code>http://foo:9080/bar/baz/mywebapp</code></td>
-	 * 	</tr>
-	 * 	<tr>
-	 * 		<td><code>http://foo:9080/bar/baz</code></td>
-	 * 		<td><code>http://mywebapp</code></td>
-	 * 		<td><code>http://mywebapp</code></td>
-	 * 	</tr>
-	 * </table>
+	 * Defines the resolution level for URIs when serializing any of the following:
+	 * <ul>
+	 * 	<li>{@link java.net.URI}
+	 * 	<li>{@link java.net.URL}
+	 * 	<li>Properties annotated with {@link org.apache.juneau.annotation.URI @URI}
+	 * </ul>
+	 * <p>
+	 * Possible values are:
+	 * <ul>
+	 * 	<li>{@link UriResolution#ABSOLUTE}
+	 * 		- Resolve to an absolute URL (e.g. <js>"http://host:port/context-root/servlet-path/path-info"</js>).
+	 * 	<li>{@link UriResolution#ROOT_RELATIVE}
+	 * 		- Resolve to a root-relative URL (e.g. <js>"/context-root/servlet-path/path-info"</js>).
+	 * 	<li>{@link UriResolution#NONE}
+	 * 		- Don't do any URL resolution.
+	 * </ul>
 	 */
-	public static final String SERIALIZER_absolutePathUriBase = "Serializer.absolutePathUriBase";
+	public static final String SERIALIZER_uriResolution = "Serializer.uriResolution";
 
 	/**
-	 * <b>Configuration property:</b>  URI context bean.
+	 * <b>Configuration property:</b>  URI relativity.
 	 * <p>
 	 * <ul>
-	 * 	<li><b>Name:</b> <js>"Serializer.uriContext"</js>
-	 * 	<li><b>Data type:</b> {@link UriContext}
-	 * 	<li><b>Default:</b> {@link UriContext#DEFAULT}
+	 * 	<li><b>Name:</b> <js>"Serializer.uriRelativity"</js>
+	 * 	<li><b>Data type:</b> {@link UriRelativity}
+	 * 	<li><b>Default:</b> {@link UriRelativity#RESOURCE}
 	 * 	<li><b>Session-overridable:</b> <jk>true</jk>
 	 * </ul>
 	 * <p>
-	 * Bean used for resolution of URIs to absolute or root-relative form.
+	 * Defines what relative URIs are relative to when serializing any of the following:
+	 * <ul>
+	 * 	<li>{@link java.net.URI}
+	 * 	<li>{@link java.net.URL}
+	 * 	<li>Properties annotated with {@link org.apache.juneau.annotation.URI @URI}
+	 * </ul>
 	 * <p>
-	 * For example, to define a URI context that causes relative URIs to be converted to root-relative form and
-	 * assumes relative URIs are relative to the servlet path:
-	 * <p class='bcode'>
-	 * 	<js>"{resolution:'ROOT_RELATIVE',relativity:'RESOURCE',contextRoot:'/myContext',servletPath:'/myServlet'}"</js>
-	 * </p>
+	 * Possible values are:
+	 * <ul>
+	 * 	<li>{@link UriRelativity#RESOURCE}
+	 * 		- Relative URIs should be considered relative to the servlet URI.
+	 * 	<li>{@link UriRelativity#PATH_INFO}
+	 * 		- Relative URIs should be considered relative to the request URI.
+	 * </ul>
 	 */
-	public static final String SERIALIZER_uriContext = "Serializer.uriContext";
+	public static final String SERIALIZER_uriRelativity = "Serializer.uriRelativity";
 
 	/**
 	 * <b>Configuration property:</b>  Sort arrays and collections alphabetically.
@@ -368,8 +350,9 @@ public class SerializerContext extends BeanContext {
 		sortMaps,
 		abridged;
 	final char quoteChar;
-	final String relativeUriBase, absolutePathUriBase;
 	final UriContext uriContext;
+	final UriResolution uriResolution;
+	final UriRelativity uriRelativity;
 
 	/**
 	 * Constructor.
@@ -392,27 +375,9 @@ public class SerializerContext extends BeanContext {
 		sortMaps = ps.getProperty(SERIALIZER_sortMaps, boolean.class, false);
 		abridged = ps.getProperty(SERIALIZER_abridged, boolean.class, false);
 		quoteChar = ps.getProperty(SERIALIZER_quoteChar, String.class, "\"").charAt(0);
-		relativeUriBase = resolveRelativeUriBase(ps.getProperty(SERIALIZER_relativeUriBase, String.class, ""));
-		absolutePathUriBase = resolveAbsolutePathUriBase(ps.getProperty(SERIALIZER_absolutePathUriBase, String.class, ""));
 		uriContext = ps.getProperty(SERIALIZER_uriContext, UriContext.class, UriContext.DEFAULT);
-	}
-
-	private static String resolveRelativeUriBase(String s) {
-		if (StringUtils.isEmpty(s))
-			return null;
-		if (s.equals("/"))
-			return s;
-		else if (StringUtils.endsWith(s, '/'))
-			s = s.substring(0, s.length()-1);
-		return s;
-	}
-
-	private static String resolveAbsolutePathUriBase(String s) {
-		if (StringUtils.isEmpty(s))
-			return null;
-		if (StringUtils.endsWith(s, '/'))
-			s = s.substring(0, s.length()-1);
-		return s;
+		uriResolution = ps.getProperty(SERIALIZER_uriResolution, UriResolution.class, UriResolution.ROOT_RELATIVE);
+		uriRelativity = ps.getProperty(SERIALIZER_uriRelativity, UriRelativity.class, UriRelativity.RESOURCE);
 	}
 
 	@Override /* Context */
@@ -433,9 +398,9 @@ public class SerializerContext extends BeanContext {
 				.append("sortMaps", sortMaps)
 				.append("parserKnowsRootTypes", abridged)
 				.append("quoteChar", quoteChar)
-				.append("relativeUriBase", relativeUriBase)
-				.append("absolutePathUriBase", absolutePathUriBase)
 				.append("uriContext", uriContext)
+				.append("uriResolution", uriResolution)
+				.append("uriRelativity", uriRelativity)
 			);
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java
index f97ed7a..bbe7577 100644
--- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java
@@ -347,25 +347,36 @@ public class SerializerGroupBuilder {
 	}
 
 	/**
-	 * Sets the {@link SerializerContext#SERIALIZER_relativeUriBase} property on all serializers in this group.
+	 * Sets the {@link SerializerContext#SERIALIZER_uriContext} property on all serializers in this group.
 	 *
 	 * @param value The new value for this property.
 	 * @return This object (for method chaining).
-	 * @see SerializerContext#SERIALIZER_relativeUriBase
+	 * @see SerializerContext#SERIALIZER_uriContext
 	 */
-	public SerializerGroupBuilder relativeUriBase(String value) {
-		return property(SERIALIZER_relativeUriBase, value);
+	public SerializerGroupBuilder uriContext(UriContext value) {
+		return property(SERIALIZER_uriContext, value);
 	}
 
 	/**
-	 * Sets the {@link SerializerContext#SERIALIZER_absolutePathUriBase} property on all serializers in this group.
+	 * Sets the {@link SerializerContext#SERIALIZER_uriResolution} property on all serializers in this group.
 	 *
 	 * @param value The new value for this property.
 	 * @return This object (for method chaining).
-	 * @see SerializerContext#SERIALIZER_absolutePathUriBase
+	 * @see SerializerContext#SERIALIZER_uriResolution
 	 */
-	public SerializerGroupBuilder absolutePathUriBase(String value) {
-		return property(SERIALIZER_absolutePathUriBase, value);
+	public SerializerGroupBuilder uriResolution(UriResolution value) {
+		return property(SERIALIZER_uriResolution, value);
+	}
+
+	/**
+	 * Sets the {@link SerializerContext#SERIALIZER_uriRelativity} property on all serializers in this group.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object (for method chaining).
+	 * @see SerializerContext#SERIALIZER_uriRelativity
+	 */
+	public SerializerGroupBuilder uriRelativity(UriRelativity value) {
+		return property(SERIALIZER_uriRelativity, value);
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java
index d89203b..9a96a30 100644
--- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java
@@ -53,8 +53,7 @@ public class SerializerSession extends BeanSession {
 		sortMaps,
 		abridged;
 	private final char quoteChar;
-	private final String relativeUriBase, absolutePathUriBase;
-	private final UriContext uriContext;
+	private final UriResolver uriResolver;
 
 	/** The current indentation depth into the model. */
 	public int indent;
@@ -102,7 +101,8 @@ public class SerializerSession extends BeanSession {
 		super(ctx, op, locale, timeZone, mediaType);
 		this.javaMethod = javaMethod;
 		this.output = output;
-		this.uriContext = (uriContext != null ? uriContext : ctx.uriContext);
+		UriResolution uriResolution;
+		UriRelativity uriRelativity;
 		if (op == null || op.isEmpty()) {
 			maxDepth = ctx.maxDepth;
 			initialDepth = ctx.initialDepth;
@@ -115,11 +115,11 @@ public class SerializerSession extends BeanSession {
 			trimEmptyMaps = ctx.trimEmptyMaps;
 			trimStrings = ctx.trimStrings;
 			quoteChar = ctx.quoteChar;
-			relativeUriBase = ctx.relativeUriBase;
-			absolutePathUriBase = ctx.absolutePathUriBase;
 			sortCollections = ctx.sortCollections;
 			sortMaps = ctx.sortMaps;
 			abridged = ctx.abridged;
+			uriResolution = ctx.uriResolution;
+			uriRelativity = ctx.uriRelativity;
 		} else {
 			maxDepth = op.getInt(SERIALIZER_maxDepth, ctx.maxDepth);
 			initialDepth = op.getInt(SERIALIZER_initialDepth, ctx.initialDepth);
@@ -132,13 +132,15 @@ public class SerializerSession extends BeanSession {
 			trimEmptyMaps = op.getBoolean(SERIALIZER_trimEmptyMaps, ctx.trimEmptyMaps);
 			trimStrings = op.getBoolean(SERIALIZER_trimStrings, ctx.trimStrings);
 			quoteChar = op.getString(SERIALIZER_quoteChar, ""+ctx.quoteChar).charAt(0);
-			relativeUriBase = op.getString(SERIALIZER_relativeUriBase, ctx.relativeUriBase);
-			absolutePathUriBase = op.getString(SERIALIZER_absolutePathUriBase, ctx.absolutePathUriBase);
 			sortCollections = op.getBoolean(SERIALIZER_sortCollections, ctx.sortMaps);
 			sortMaps = op.getBoolean(SERIALIZER_sortMaps, ctx.sortMaps);
 			abridged = op.getBoolean(SERIALIZER_abridged, ctx.abridged);
+			uriResolution = op.get(UriResolution.class, SERIALIZER_uriResolution, UriResolution.ROOT_RELATIVE);
+			uriRelativity = op.get(UriRelativity.class, SERIALIZER_uriRelativity, UriRelativity.RESOURCE);
 		}
 
+		uriResolver = new UriResolver(uriResolution, uriRelativity, uriContext == null ? ctx.uriContext : uriContext);
+
 		this.indent = initialDepth;
 		if (detectRecursions || isDebug()) {
 			set = new IdentityHashMap<Object,Object>();
@@ -244,12 +246,12 @@ public class SerializerSession extends BeanSession {
 	}
 
 	/**
-	 * Returns the URI context passed in to this constructor.
+	 * Returns the URI resolver.
 	 *
-	 * @return The URI context passed in to this constructor.
+	 * @return The URI resolver.
 	 */
-	public final UriContext getUriContext() {
-		return uriContext;
+	public final UriResolver getUriResolver() {
+		return uriResolver;
 	}
 
 	/**
@@ -370,24 +372,6 @@ public class SerializerSession extends BeanSession {
 	}
 
 	/**
-	 * Returns the {@link SerializerContext#SERIALIZER_relativeUriBase} setting value for this session.
-	 *
-	 * @return The {@link SerializerContext#SERIALIZER_relativeUriBase} setting value for this session.
-	 */
-	public final String getRelativeUriBase() {
-		return relativeUriBase;
-	}
-
-	/**
-	 * Returns the {@link SerializerContext#SERIALIZER_absolutePathUriBase} setting value for this session.
-	 *
-	 * @return The {@link SerializerContext#SERIALIZER_absolutePathUriBase} setting value for this session.
-	 */
-	public final String getAbsolutePathUriBase() {
-		return absolutePathUriBase;
-	}
-
-	/**
 	 * Push the specified object onto the stack.
 	 *
 	 * @param attrName The attribute name.
@@ -578,30 +562,67 @@ public class SerializerSession extends BeanSession {
 	}
 
 	/**
-	 * Converts a String to an absolute URI based on the {@link SerializerContext#SERIALIZER_absolutePathUriBase} and
-	 * 	{@link SerializerContext#SERIALIZER_relativeUriBase} settings on this context.
+	 * Converts a String to an absolute URI based on the {@link UriContext} on this session.
 	 *
 	 * @param uri The input URI.
+	 * 	Can be any of the following:
+	 * 	<ul>
+	 * 		<li>{@link java.net.URI}
+	 * 		<li>{@link java.net.URL}
+	 * 		<li>{@link CharSequence}
+	 * 	</ul>
+	 * 	URI can be any of the following forms:
+	 * 	<ul>
+	 * 		<li><js>"foo://foo"</js> - Absolute URI.
+	 * 		<li><js>"/foo"</js> - Root-relative URI.
+	 * 		<li><js>"/"</js> - Root URI.
+	 * 		<li><js>"context:/foo"</js> - Context-root-relative URI.
+	 * 		<li><js>"context:/"</js> - Context-root URI.
+	 * 		<li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
+	 * 		<li><js>"servlet:/"</js> - Servlet-path URI.
+	 * 		<li><js>"request:/foo"</js> - Request-path-relative URI.
+	 * 		<li><js>"request:/"</js> - Request-path URI.
+	 * 		<li><js>"foo"</js> - Path-info-relative URI.
+	 * 		<li><js>""</js> - Path-info URI.
+	 * 	</ul>
 	 * @return The resolved URI.
 	 */
-	public String resolveUri(String uri) {
-		if (uri.indexOf("://") != -1 || (absolutePathUriBase == null && relativeUriBase == null))
-			return uri;
-		StringBuilder sb = getStringBuilder();
-		if (StringUtils.startsWith(uri, '/')) {
-			if (absolutePathUriBase != null)
-				sb.append(absolutePathUriBase);
-		} else {
-			if (relativeUriBase != null) {
-				sb.append(relativeUriBase);
-				if (! uri.equals("/"))
-					sb.append("/");
-			}
-		}
-		sb.append(uri);
-		String s = sb.toString();
-		returnStringBuilder(sb);
-		return s;
+	public String resolveUri(Object uri) {
+		return uriResolver.resolve(uri);
+	}
+
+	/**
+	 * Opposite of {@link #resolveUri(Object)}.
+	 * <p>
+	 * Converts the URI to a value relative to the specified <code>relativeTo</code> parameter.
+	 * <p>
+	 * Both parameters can be any of the following:
+	 * <ul>
+	 * 	<li>{@link java.net.URI}
+	 * 	<li>{@link java.net.URL}
+	 * 	<li>{@link CharSequence}
+	 * </ul>
+	 * Both URIs can be any of the following forms:
+	 * <ul>
+	 * 	<li><js>"foo://foo"</js> - Absolute URI.
+	 * 	<li><js>"/foo"</js> - Root-relative URI.
+	 * 	<li><js>"/"</js> - Root URI.
+	 * 	<li><js>"context:/foo"</js> - Context-root-relative URI.
+	 * 	<li><js>"context:/"</js> - Context-root URI.
+	 * 	<li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
+	 * 	<li><js>"servlet:/"</js> - Servlet-path URI.
+	 * 	<li><js>"request:/foo"</js> - Request-path-relative URI.
+	 * 	<li><js>"request:/"</js> - Request-path URI.
+	 * 	<li><js>"foo"</js> - Path-info-relative URI.
+	 * 	<li><js>""</js> - Path-info URI.
+	 * </ul>
+	 *
+	 * @param relativeTo The URI to relativize against.
+	 * @param uri The URI to relativize.
+	 * @return The relativized URI.
+	 */
+	public String relativizeUri(Object relativeTo, Object uri) {
+		return uriResolver.relativize(relativeTo, uri);
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerWriter.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerWriter.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerWriter.java
index 413aa63..3250d1a 100644
--- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerWriter.java
+++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerWriter.java
@@ -16,7 +16,6 @@ import java.io.*;
 import java.net.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.internal.*;
 
 /**
  * Simple wrapper around a standard {@link Writer} with additional methods.
@@ -43,14 +42,8 @@ public class SerializerWriter extends Writer {
 	/** The quote character being used by this writer. */
 	protected final char quoteChar;
 
-	/** The base (e.g. <js>https://localhost:9443/contextPath"</js>) for relative URIs (e.g. <js>"my/path"</js>). */
-	protected final String relativeUriBase;
-
-	/** The base (e.g. <js>https://localhost:9443"</js>) for relative URIs with absolute paths (e.g. <js>"/contextPath/my/path"</js>). */
-	protected final String absolutePathUriBase;
-
-	/** The URI context of the request. (i.e. the REST request URL broken down into authority/context/servlet/pathInfo parts. */
-	protected final UriContext uriContext;
+	/** The URI resolver of the request. */
+	protected final UriResolver uriResolver;
 
 	/**
 	 * @param out The writer being wrapped.
@@ -58,19 +51,14 @@ public class SerializerWriter extends Writer {
 	 * 	{@link #s()} will write a space character.
 	 * @param trimStrings If <jk>true</jk>, strings should be trimmed before they're serialized.
 	 * @param quoteChar The character to write when {@link #q()} is called.
-	 * @param relativeUriBase The base (e.g. <js>https://localhost:9443/contextPath"</js>) for relative URIs (e.g. <js>"my/path"</js>).
-	 * @param absolutePathUriBase The base (e.g. <js>https://localhost:9443"</js>) for relative URIs with absolute paths (e.g. <js>"/contextPath/my/path"</js>).
-	 * @param uriContext The URI context.
-	 * 	Identifies the current request URI used for resolution of URIs to absolute or root-relative form.
+	 * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form.
 	 */
-	public SerializerWriter(Writer out, boolean useWhitespace, boolean trimStrings, char quoteChar, String relativeUriBase, String absolutePathUriBase, UriContext uriContext) {
+	public SerializerWriter(Writer out, boolean useWhitespace, boolean trimStrings, char quoteChar, UriResolver uriResolver) {
 		this.out = out;
 		this.useWhitespace = useWhitespace;
 		this.trimStrings = trimStrings;
 		this.quoteChar = quoteChar;
-		this.relativeUriBase = relativeUriBase;
-		this.absolutePathUriBase = absolutePathUriBase;
-		this.uriContext = uriContext != null ? uriContext : new UriContext();
+		this.uriResolver = uriResolver;
 	}
 
 	/**
@@ -160,32 +148,17 @@ public class SerializerWriter extends Writer {
 	 * Object is converted to a <code>String</code> using <code>toString()</code>, so this will work on {@link URL} or {@link URI} objects,
 	 * or any other type that returns a URI via it's <code>toString()</code> method.
 	 * <p>
-	 * If the URI is relative (i.e. without a schema and not prepended with <js>'/'</js>) the URI
-	 * will be prepended with {@link #absolutePathUriBase} and {@link #relativeUriBase}.
-	 * <p>
-	 * If the URI is context-absolute (i.e. without a schema, but prepended with <js>'/'</js>)
-	 * the URI will be prepended with {@link #absolutePathUriBase}.
-	 *
+	 * The URI is resolved based on the {@link SerializerContext#SERIALIZER_uriRelativity} and
+	 * {@link SerializerContext#SERIALIZER_uriResolution} settings and the {@link UriContext} that's part of the 
+	 * session.
+	 * 
 	 * @param uri The URI to serialize.
 	 * @return This object (for method chaining).
 	 * @throws IOException If a problem occurred trying to write to the writer.
 	 */
 	public SerializerWriter appendUri(Object uri) throws IOException {
-		String s = uri.toString();
-		if (s.indexOf("://") == -1) {
-			if (StringUtils.startsWith(s, '/')) {
-				if (absolutePathUriBase != null)
-					append(absolutePathUriBase);
-			} else {
-				if (relativeUriBase != null) {
-					append(relativeUriBase);
-					if (! relativeUriBase.equals("/"))
-						append("/");
-
-				}
-			}
-		}
-		return append(s);
+		uriResolver.append(this, uri);
+		return this;
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerBuilder.java b/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerBuilder.java
index 7436ddc..015e662 100644
--- a/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerBuilder.java
@@ -188,14 +188,20 @@ public class SoapXmlSerializerBuilder extends XmlSerializerBuilder {
 	}
 
 	@Override /* SerializerBuilder */
-	public SoapXmlSerializerBuilder relativeUriBase(String value) {
-		super.relativeUriBase(value);
+	public SoapXmlSerializerBuilder uriContext(UriContext value) {
+		super.uriContext(value);
 		return this;
 	}
 
 	@Override /* SerializerBuilder */
-	public SoapXmlSerializerBuilder absolutePathUriBase(String value) {
-		super.absolutePathUriBase(value);
+	public SoapXmlSerializerBuilder uriResolution(UriResolution value) {
+		super.uriResolution(value);
+		return this;
+	}
+
+	@Override /* SerializerBuilder */
+	public SoapXmlSerializerBuilder uriRelativity(UriRelativity value) {
+		super.uriRelativity(value);
 		return this;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerBuilder.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerBuilder.java
index 2106637..3d0ee3d 100644
--- a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerBuilder.java
@@ -169,14 +169,20 @@ public class UonSerializerBuilder extends SerializerBuilder {
 	}
 
 	@Override /* SerializerBuilder */
-	public UonSerializerBuilder relativeUriBase(String value) {
-		super.relativeUriBase(value);
+	public UonSerializerBuilder uriContext(UriContext value) {
+		super.uriContext(value);
 		return this;
 	}
 
 	@Override /* SerializerBuilder */
-	public UonSerializerBuilder absolutePathUriBase(String value) {
-		super.absolutePathUriBase(value);
+	public UonSerializerBuilder uriResolution(UriResolution value) {
+		super.uriResolution(value);
+		return this;
+	}
+
+	@Override /* SerializerBuilder */
+	public UonSerializerBuilder uriRelativity(UriRelativity value) {
+		super.uriRelativity(value);
 		return this;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
index 0f9b5a1..e9f9659 100644
--- a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
@@ -87,6 +87,6 @@ public class UonSerializerSession extends SerializerSession {
 		Object output = getOutput();
 		if (output instanceof UonWriter)
 			return (UonWriter)output;
-		return new UonWriter(this, super.getWriter(), isUseWhitespace(), isEncodeChars(), isTrimStrings(), getRelativeUriBase(), getAbsolutePathUriBase(), getUriContext());
+		return new UonWriter(this, super.getWriter(), isUseWhitespace(), isEncodeChars(), isTrimStrings(), getUriResolver());
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java
index 85f9cd0..2844e62 100644
--- a/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java
@@ -53,13 +53,10 @@ public final class UonWriter extends SerializerWriter {
 	 * @param useWhitespace If <jk>true</jk>, tabs will be used in output.
 	 * @param encodeChars If <jk>true</jk>, special characters should be encoded.
 	 * @param trimStrings If <jk>true</jk>, strings should be trimmed before they're serialized.
-	 * @param relativeUriBase The base (e.g. <js>https://localhost:9443/contextPath"</js>) for relative URIs (e.g. <js>"my/path"</js>).
-	 * @param absolutePathUriBase The base (e.g. <js>https://localhost:9443"</js>) for relative URIs with absolute paths (e.g. <js>"/contextPath/my/path"</js>).
-	 * @param uriContext The URI context.
-	 * 	Identifies the current request URI used for resolution of URIs to absolute or root-relative form.
+	 * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form.
 	 */
-	protected UonWriter(UonSerializerSession session, Writer out, boolean useWhitespace, boolean encodeChars, boolean trimStrings, String relativeUriBase, String absolutePathUriBase, UriContext uriContext) {
-		super(out, useWhitespace, trimStrings, '\'', relativeUriBase, absolutePathUriBase, uriContext);
+	protected UonWriter(UonSerializerSession session, Writer out, boolean useWhitespace, boolean encodeChars, boolean trimStrings, UriResolver uriResolver) {
+		super(out, useWhitespace, trimStrings, '\'', uriResolver);
 		this.session = session;
 		this.encodeChars = encodeChars;
 	}
@@ -167,20 +164,7 @@ public final class UonWriter extends SerializerWriter {
 	 */
 	@Override
 	public SerializerWriter appendUri(Object uri) throws IOException {
-		String s = uri.toString();
-		if (s.indexOf("://") == -1) {
-			if (StringUtils.startsWith(s, '/')) {
-				if (absolutePathUriBase != null)
-					append(absolutePathUriBase);
-			} else {
-				if (relativeUriBase != null) {
-					append(relativeUriBase);
-					if (! relativeUriBase.equals("/"))
-						append("/");
-				}
-			}
-		}
-		return appendObject(s, false, false);
+		return appendObject(uriResolver.resolve(uri), false, false);
 	}
 
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java
index e795a48..7c79631 100644
--- a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java
@@ -231,14 +231,20 @@ public class UrlEncodingSerializerBuilder extends UonSerializerBuilder {
 	}
 
 	@Override /* SerializerBuilder */
-	public UrlEncodingSerializerBuilder relativeUriBase(String value) {
-		super.relativeUriBase(value);
+	public UrlEncodingSerializerBuilder uriContext(UriContext value) {
+		super.uriContext(value);
 		return this;
 	}
 
 	@Override /* SerializerBuilder */
-	public UrlEncodingSerializerBuilder absolutePathUriBase(String value) {
-		super.absolutePathUriBase(value);
+	public UrlEncodingSerializerBuilder uriResolution(UriResolution value) {
+		super.uriResolution(value);
+		return this;
+	}
+
+	@Override /* SerializerBuilder */
+	public UrlEncodingSerializerBuilder uriRelativity(UriRelativity value) {
+		super.uriRelativity(value);
 		return this;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java
index 0e42c27..9a499d2 100644
--- a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializer.java
@@ -266,7 +266,7 @@ public class XmlSchemaSerializer extends XmlSerializer {
 			this.defaultNs = defaultNs;
 			this.targetNs = targetNs;
 			this.session = session;
-			w = new XmlWriter(sw, session.isUseWhitespace(), session.isTrimStrings(), session.getQuoteChar(), null, null, null, true, null);
+			w = new XmlWriter(sw, session.isUseWhitespace(), session.isTrimStrings(), session.getQuoteChar(), null, true, null);
 			int i = session.getIndent();
 			w.oTag(i, "schema");
 			w.attr("xmlns", xs.getUri());
@@ -418,7 +418,7 @@ public class XmlSchemaSerializer extends XmlSerializer {
 									Namespace cNs = first(xmlMeta.getNamespace(), ct2.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs);
 									if (xmlMeta.getNamespace() == null) {
 										w.oTag(i+2, "element")
-											.attr("name", XmlUtils.encodeElementName(childName), true)
+											.attr("name", XmlUtils.encodeElementName(childName), false)
 											.attr("type", getXmlType(cNs, ct2))
 											.attr("minOccurs", 0);
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerBuilder.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerBuilder.java
index 4af4739..9e24414 100644
--- a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerBuilder.java
@@ -168,14 +168,20 @@ public class XmlSchemaSerializerBuilder extends XmlSerializerBuilder {
 	}
 
 	@Override /* SerializerBuilder */
-	public XmlSchemaSerializerBuilder relativeUriBase(String value) {
-		super.relativeUriBase(value);
+	public XmlSchemaSerializerBuilder uriContext(UriContext value) {
+		super.uriContext(value);
 		return this;
 	}
 
 	@Override /* SerializerBuilder */
-	public XmlSchemaSerializerBuilder absolutePathUriBase(String value) {
-		super.absolutePathUriBase(value);
+	public XmlSchemaSerializerBuilder uriResolution(UriResolution value) {
+		super.uriResolution(value);
+		return this;
+	}
+
+	@Override /* SerializerBuilder */
+	public XmlSchemaSerializerBuilder uriRelativity(UriRelativity value) {
+		super.uriRelativity(value);
 		return this;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java
index cd46134..af5ce51 100644
--- a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializer.java
@@ -520,7 +520,7 @@ public class XmlSerializer extends WriterSerializer {
 		// Render the tag contents.
 		if (o != null) {
 			if (sType.isUri() || (pMeta != null && pMeta.isUri())) {
-				out.appendUri(o);
+				out.textUri(o);
 			} else if (sType.isCharSequence() || sType.isChar()) {
 				if (format == XMLTEXT)
 					out.append(o);

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerBuilder.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerBuilder.java
index dfdef5a..2e812b9 100644
--- a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerBuilder.java
@@ -315,14 +315,20 @@ public class XmlSerializerBuilder extends SerializerBuilder {
 	}
 
 	@Override /* SerializerBuilder */
-	public XmlSerializerBuilder relativeUriBase(String value) {
-		super.relativeUriBase(value);
+	public XmlSerializerBuilder uriContext(UriContext value) {
+		super.uriContext(value);
 		return this;
 	}
 
 	@Override /* SerializerBuilder */
-	public XmlSerializerBuilder absolutePathUriBase(String value) {
-		super.absolutePathUriBase(value);
+	public XmlSerializerBuilder uriResolution(UriResolution value) {
+		super.uriResolution(value);
+		return this;
+	}
+
+	@Override /* SerializerBuilder */
+	public XmlSerializerBuilder uriRelativity(UriRelativity value) {
+		super.uriRelativity(value);
 		return this;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
index ef08f48..bd99974 100644
--- a/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
@@ -200,6 +200,6 @@ public class XmlSerializerSession extends SerializerSession {
 		Object output = getOutput();
 		if (output instanceof XmlWriter)
 			return (XmlWriter)output;
-		return new XmlWriter(super.getWriter(), isUseWhitespace(), isTrimStrings(), getQuoteChar(), getRelativeUriBase(), getAbsolutePathUriBase(), getUriContext(), isEnableNamespaces(), getDefaultNamespace());
+		return new XmlWriter(super.getWriter(), isUseWhitespace(), isTrimStrings(), getQuoteChar(), getUriResolver(), isEnableNamespaces(), getDefaultNamespace());
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/xml/XmlUtils.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlUtils.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlUtils.java
index 0567d73..483e06d 100644
--- a/juneau-core/src/main/java/org/apache/juneau/xml/XmlUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlUtils.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.xml;
 
 import java.io.*;
+import java.net.*;
 import java.util.*;
 
 import javax.xml.stream.*;
@@ -27,16 +28,110 @@ import org.apache.juneau.xml.annotation.*;
 public final class XmlUtils {
 
 	//--------------------------------------------------------------------------------
-	// Encode URI part
+	// XML element names
 	//--------------------------------------------------------------------------------
 
 	/**
-	 * Encodes invalid XML text characters to <code>_x####_</code> sequences.
+	 * Encodes any invalid XML element name characters to <code>_x####_</code> sequences.
+	 *
+	 * @param w The writer to send the output to.
+	 * @param o The object being encoded.
+	 * @return The same writer passed in.
+	 * @throws IOException Throw by the writer.
+	 */
+	public static final Writer encodeElementName(Writer w, Object o) throws IOException {
+
+		if (o == null)
+			return w.append("_x0000_");
+
+		String s = o.toString();
+
+		if (needsElementNameEncoding(s))
+			return encodeElementNameInner(w, s);
+
+		w.append(s);
+		return w;
+	}
+
+	/**
+	 * Encodes any invalid XML element name characters to <code>_x####_</code> sequences.
+	 *
+	 * @param o The object being encoded.
+	 * @return The encoded element name string.
+	 */
+	public static final String encodeElementName(Object o) {
+		if (o == null)
+			return "_x0000_";
+
+		String s = o.toString();
+		if (s.isEmpty())
+			return "_xE000_";
+		try {
+			if (needsElementNameEncoding(s))
+				return encodeElementNameInner(new StringBuilderWriter(s.length() * 2), s).toString();
+		} catch (IOException e) {
+			throw new RuntimeException(e); // Never happens
+		}
+
+		return s;
+	}
+
+	private static final Writer encodeElementNameInner(Writer w, String s) throws IOException {
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			if ((c >= 'A' && c <= 'Z')
+					|| (c == '_' && ! isEscapeSequence(s,i))
+					|| (c >= 'a' && c <= 'z')
+					|| (i != 0 && (
+							c == '-'
+							|| c == '.'
+							|| (c >= '0' && c <= '9')
+							|| c == '\u00b7'
+							|| (c >= '\u0300' && c <= '\u036f')
+							|| (c >= '\u203f' && c <= '\u2040')
+						))
+					|| (c >= '\u00c0' && c <= '\u00d6')
+					|| (c >= '\u00d8' && c <= '\u00f6')
+					|| (c >= '\u00f8' && c <= '\u02ff')
+					|| (c >= '\u0370' && c <= '\u037d')
+					|| (c >= '\u037f' && c <= '\u1fff')
+					|| (c >= '\u200c' && c <= '\u200d')
+					|| (c >= '\u2070' && c <= '\u218f')
+					|| (c >= '\u2c00' && c <= '\u2fef')
+					|| (c >= '\u3001' && c <= '\ud7ff')
+					|| (c >= '\uf900' && c <= '\ufdcf')
+					|| (c >= '\ufdf0' && c <= '\ufffd')) {
+				w.append(c);
+			}  else {
+				appendPaddedHexChar(w, c);
+			}
+		}
+		return w;
+	}
+
+	private static final boolean needsElementNameEncoding(String s) {
+		// Note that this doesn't need to be perfect, just fast.
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			if (! (c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'))
+				return true;
+			if (i == 0 && (c >= '0' && c <= '9'))
+				return true;
+		}
+		return false;
+	}
+
+	//--------------------------------------------------------------------------------
+	// XML element text
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Escapes invalid XML text characters to <code>_x####_</code> sequences.
 	 *
 	 * @param o The object being encoded.
 	 * @return The encoded string.
 	 */
-	public static final String encodeInvalidCharsForText(Object o) {
+	public static final String escapeText(Object o) {
 
 		if (o == null)
 			return "_x0000_";
@@ -66,10 +161,12 @@ public final class XmlUtils {
 	}
 
 	/**
+	 * Encodes the specified element text and sends the results to the specified writer.
+	 * <p>
 	 * Encodes any invalid XML text characters to <code>_x####_</code> sequences and sends the response
 	 * 	to the specified writer.
-	 * Encodes <js>'&amp;'</js>, <js>'&lt;'</js>, and <js>'&gt;'</js> as XML entities.<br>
-	 * Encodes invalid XML text characters to <code>_x####_</code> sequences.
+	 * <br>Encodes <js>'&amp;'</js>, <js>'&lt;'</js>, and <js>'&gt;'</js> as XML entities.<br>
+	 * <br>Encodes invalid XML text characters to <code>_x####_</code> sequences.
 	 *
 	 * @param w The writer to send the output to.
 	 * @param o The object being encoded.
@@ -97,18 +194,12 @@ public final class XmlUtils {
 				char c = s.charAt(i);
 				if ((i == 0 || i == len-1) && Character.isWhitespace(c) && ! preserveWhitespace)
 					appendPaddedHexChar(w, c);
-				else if (c == '&')
-					w.append("&amp;");
-				else if (c == '<')
-					w.append("&lt;");
-				else if (c == '>')
-					w.append("&gt;");
+				else if (REPLACE_TEXT.contains(c))
+					w.append(REPLACE_TEXT.get(c));
 				else if (c == '_' && isEscapeSequence(s,i))
 					appendPaddedHexChar(w, c);
 				else if (isValidXmlCharacter(c))
 					w.append(c);
-				else if (c == 0x09 || c == 0x0A || c == 0x0D)
-					w.append("&#x000").append(Integer.toHexString(c)).append(";");
 				else
 					appendPaddedHexChar(w, c);
 			}
@@ -119,7 +210,6 @@ public final class XmlUtils {
 		return w;
 	}
 
-
 	private static final boolean needsTextEncoding(String s) {
 		// See if we need to convert the string.
 		// Conversion is somewhat expensive, so make sure we need to do so before hand.
@@ -128,116 +218,115 @@ public final class XmlUtils {
 			char c = s.charAt(i);
 			if ((i == 0 || i == len-1) && Character.isWhitespace(c))
 				return true;
-			if (c == '&' || c == '<' || c == '>' || c == '\n' || ! isValidXmlCharacter(c) || (c == '_' && isEscapeSequence(s,i)))
+			if (REPLACE_TEXT.contains(c) || ! isValidXmlCharacter(c) || (c == '_' && isEscapeSequence(s,i)))
 				return true;
 		}
 		return false;
 	}
 
+	private static AsciiMap REPLACE_TEXT = new AsciiMap()
+		.append('&', "&amp;")
+		.append('<', "&lt;")
+		.append('>', "&gt;")
+		.append((char)0x09, "&#x0009;")
+		.append((char)0x0A, "&#x000a;")
+		.append((char)0x0D, "&#x000d;");
+
 
 	//--------------------------------------------------------------------------------
-	// Decode XML text
+	// XML attribute names
 	//--------------------------------------------------------------------------------
 
 	/**
-	 * Translates any _x####_ sequences (introduced by the various encode methods) back into their original characters.
+	 * Serializes and encodes the specified object as valid XML attribute name.
 	 *
-	 * @param s The string being decoded.
-	 * @param sb The string builder to use as a scratch pad.
-	 * @return The decoded string.
+	 * @param w The writer to send the output to.
+	 * @param o The object being serialized.
+	 * @return This object (for method chaining).
+	 * @throws IOException If a problem occurred.
 	 */
-	public static final String decode(String s, StringBuilder sb) {
-		if (s == null) return null;
-		if (s.length() == 0)
-			return s;
-		if (s.indexOf('_') == -1)
-			return s;
-
-		if (sb == null)
-			sb = new StringBuilder(s.length());
-		for (int i = 0; i < s.length(); i++) {
-			char c = s.charAt(i);
-			if (c == '_' && isEscapeSequence(s,i)) {
+	public static final Writer encodeAttrName(Writer w, Object o) throws IOException {
 
-				int x = Integer.parseInt(s.substring(i+2, i+6), 16);
+		if (o == null)
+			return w.append("_x0000_");
 
-				// If we find _x0000_, then that means a null.
-				// If we find _xE000_, then that means an empty string.
-				if (x == 0)
-					return null;
-				else if (x != 0xE000)
-					sb.append((char)x);
+		String s = o.toString();
 
-				i+=6;
-			} else {
-				sb.append(c);
+		if (needsAttrNameEncoding(s)) {
+			for (int i = 0; i < s.length(); i++) {
+				char c = s.charAt(i);
+				if (i == 0) {
+					if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == ':')
+						w.append(c);
+					else if (c == '_' && ! isEscapeSequence(s,i))
+						w.append(c);
+					else
+						appendPaddedHexChar(w, c);
+				} else {
+					if ((c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == ':'))
+						w.append(c);
+					else if (c == '_' && ! isEscapeSequence(s,i))
+						w.append(c);
+					else
+						appendPaddedHexChar(w, c);
+				}
 			}
+		} else {
+			w.append(s);
 		}
-		return sb.toString();
-	}
-
 
-	/**
-	 * Given a list of Strings and other Objects, combines Strings that are next to each other in the list.
-	 *
-	 * @param l The list of text nodes to collapse.
-	 * @return The same list.
-	 */
-	public static LinkedList<Object> collapseTextNodes(LinkedList<Object> l) {
+		return w;
+	}
 
-		String prev = null;
-		for (ListIterator<Object> i = l.listIterator(); i.hasNext();) {
-			Object o = i.next();
-			if (o instanceof String) {
-				if (prev == null)
-					prev = o.toString();
-				else {
-					prev += o;
-					i.remove();
-					i.previous();
-					i.remove();
-					i.add(prev);
-				}
-			} else {
-				prev = null;
-			}
+	private static final boolean needsAttrNameEncoding(String s) {
+		// Note that this doesn't need to be perfect, just fast.
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			if (! (c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'))
+				return true;
+			if (i == 0 && ! (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'))
+				return true;
 		}
-		return l;
+		return false;
 	}
 
-
 	//--------------------------------------------------------------------------------
-	// Encode XML attributes
+	// XML attribute values
 	//--------------------------------------------------------------------------------
 
 	/**
-	 * Serializes and encodes the specified object as valid XML attribute name.
+	 * Encodes the specified attribute value and sends the results to the specified writer.
+	 * <p>
+	 * Encodes any invalid XML text characters to <code>_x####_</code> sequences and sends the response
+	 * 	to the specified writer.
+	 * <br>Encodes <js>'&amp;'</js>, <js>'&lt;'</js>, <js>'&gt;'</js>, <js>'"'</js>, and <js>'\''</js> as XML entities.<br>
+	 * <br?Encodes invalid XML text characters to <code>_x####_</code> sequences.
 	 *
 	 * @param w The writer to send the output to.
-	 * @param o The object being serialized.
-	 * @return This object (for method chaining).
-	 * @throws IOException If a problem occurred.
+	 * @param o The object being encoded.
+	 * @param trim Trim the text before serializing it.
+	 * If <jk>true</jk>, leading and trailing whitespace characters will be encoded.
+	 * @return The same writer passed in.
+	 * @throws IOException Thrown from the writer.
 	 */
-	public static final Writer encodeAttr(Writer w, Object o) throws IOException {
-
+	public static final Writer encodeAttrValue(Writer w, Object o, boolean trim) throws IOException {
 		if (o == null)
 			return w.append("_x0000_");
 
 		String s = o.toString();
+		if (s.isEmpty())
+			return w;
+		if (trim)
+			s = s.trim();
 
-		if (needsAttributeEncoding(s)) {
-			for (int i = 0; i < s.length(); i++) {
+		if (needsAttrValueEncoding(s)) {
+			final int len = s.length();
+			for (int i = 0; i < len; i++) {
 				char c = s.charAt(i);
-				if (c == '&')
-					w.append("&amp;");
-				else if (c == '<')
-					w.append("&lt;");
-				else if (c == '>')
-					w.append("&gt;");
-				else if (c == '\'')
-					w.append("&apos;");
-				else if (c == '"')
-					w.append("&quot;");
+				if ((i == 0 || i == len-1) && Character.isWhitespace(c))
+					appendPaddedHexChar(w, c);
+				else if (REPLACE_ATTR_VAL.contains(c))
+					w.append(REPLACE_ATTR_VAL.get(c));
 				else if (c == '_' && isEscapeSequence(s,i))
 					appendPaddedHexChar(w, c);
 				else if (isValidXmlCharacter(c))
@@ -252,114 +341,101 @@ public final class XmlUtils {
 		return w;
 	}
 
-
-	private static boolean needsAttributeEncoding(String s) {
+	private static final boolean needsAttrValueEncoding(String s) {
 		// See if we need to convert the string.
 		// Conversion is somewhat expensive, so make sure we need to do so before hand.
-		for (int i = 0; i < s.length(); i++) {
+		final int len = s.length();
+		for (int i = 0; i < len; i++) {
 			char c = s.charAt(i);
-			if (c == '&' || c == '<' || c == '>' || c == '\n' || c == '\'' || c == '"' || ! isValidXmlCharacter(c))
+			if ((i == 0 || i == len-1) && Character.isWhitespace(c))
+				return true;
+			if (REPLACE_ATTR_VAL.contains(c) || ! isValidXmlCharacter(c) || (c == '_' && isEscapeSequence(s,i)))
 				return true;
 		}
 		return false;
 	}
 
+	private static AsciiMap REPLACE_ATTR_VAL = new AsciiMap()
+		.append('&', "&amp;")
+		.append('<', "&lt;")
+		.append('>', "&gt;")
+		.append('"', "&quot;")
+		.append('\'', "&apos;")
+		.append((char)0x09, "&#x0009;")
+		.append((char)0x0A, "&#x000a;")
+		.append((char)0x0D, "&#x000d;");
+
 
 	//--------------------------------------------------------------------------------
-	// Encode XML element names
+	// Decode XML text
 	//--------------------------------------------------------------------------------
 
 	/**
-	 * Encodes any invalid XML element name characters to <code>_x####_</code> sequences.
+	 * Translates any _x####_ sequences (introduced by the various encode methods) back into their original characters.
 	 *
-	 * @param w The writer to send the output to.
-	 * @param o The object being encoded.
-	 * @return The same writer passed in.
-	 * @throws IOException Throw by the writer.
+	 * @param s The string being decoded.
+	 * @param sb The string builder to use as a scratch pad.
+	 * @return The decoded string.
 	 */
-	public static final Writer encodeElementName(Writer w, Object o) throws IOException {
+	public static final String decode(String s, StringBuilder sb) {
+		if (s == null) return null;
+		if (s.length() == 0)
+			return s;
+		if (s.indexOf('_') == -1)
+			return s;
 
-		if (o == null)
-			return w.append("_x0000_");
+		if (sb == null)
+			sb = new StringBuilder(s.length());
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			if (c == '_' && isEscapeSequence(s,i)) {
 
-		String s = o.toString();
+				int x = Integer.parseInt(s.substring(i+2, i+6), 16);
 
-		if (needsElementNameEncoding(s))
-			return encodeElementNameInner(w, s);
+				// If we find _x0000_, then that means a null.
+				// If we find _xE000_, then that means an empty string.
+				if (x == 0)
+					return null;
+				else if (x != 0xE000)
+					sb.append((char)x);
 
-		w.append(s);
-		return w;
+				i+=6;
+			} else {
+				sb.append(c);
+			}
+		}
+		return sb.toString();
 	}
 
+
 	/**
-	 * Encodes any invalid XML element name characters to <code>_x####_</code> sequences.
+	 * Given a list of Strings and other Objects, combines Strings that are next to each other in the list.
 	 *
-	 * @param o The object being encoded.
-	 * @return The encoded element name string.
+	 * @param l The list of text nodes to collapse.
+	 * @return The same list.
 	 */
-	public static final String encodeElementName(Object o) {
-		if (o == null)
-			return "_x0000_";
-
-		String s = o.toString();
-		if (s.isEmpty())
-			return "_xE000_";
-		try {
-			if (needsElementNameEncoding(s))
-				return encodeElementNameInner(new StringBuilderWriter(s.length() * 2), s).toString();
-		} catch (IOException e) {
-			throw new RuntimeException(e); // Never happens
-		}
-
-		return s;
-	}
+	public static LinkedList<Object> collapseTextNodes(LinkedList<Object> l) {
 
-	private static final Writer encodeElementNameInner(Writer w, String s) throws IOException {
-		for (int i = 0; i < s.length(); i++) {
-			char c = s.charAt(i);
-			if ((c >= 'A' && c <= 'Z')
-					|| (c == '_' && ! isEscapeSequence(s,i))
-					|| (c >= 'a' && c <= 'z')
-					|| (i != 0 && (
-							c == '-'
-							|| c == '.'
-							|| (c >= '0' && c <= '9')
-							|| c == '\u00b7'
-							|| (c >= '\u0300' && c <= '\u036f')
-							|| (c >= '\u203f' && c <= '\u2040')
-						))
-					|| (c >= '\u00c0' && c <= '\u00d6')
-					|| (c >= '\u00d8' && c <= '\u00f6')
-					|| (c >= '\u00f8' && c <= '\u02ff')
-					|| (c >= '\u0370' && c <= '\u037d')
-					|| (c >= '\u037f' && c <= '\u1fff')
-					|| (c >= '\u200c' && c <= '\u200d')
-					|| (c >= '\u2070' && c <= '\u218f')
-					|| (c >= '\u2c00' && c <= '\u2fef')
-					|| (c >= '\u3001' && c <= '\ud7ff')
-					|| (c >= '\uf900' && c <= '\ufdcf')
-					|| (c >= '\ufdf0' && c <= '\ufffd')) {
-				w.append(c);
-			}  else {
-				appendPaddedHexChar(w, c);
+		String prev = null;
+		for (ListIterator<Object> i = l.listIterator(); i.hasNext();) {
+			Object o = i.next();
+			if (o instanceof String) {
+				if (prev == null)
+					prev = o.toString();
+				else {
+					prev += o;
+					i.remove();
+					i.previous();
+					i.remove();
+					i.add(prev);
+				}
+			} else {
+				prev = null;
 			}
 		}
-		return w;
-	}
-
-	private static final boolean needsElementNameEncoding(String s) {
-		// Note that this doesn't need to be perfect, just fast.
-		for (int i = 0; i < s.length(); i++) {
-			char c = s.charAt(i);
-			if (! (c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'))
-				return true;
-			if (i == 0 && (c >= '0' && c <= '9'))
-				return true;
-		}
-		return false;
+		return l;
 	}
 
-
 	//--------------------------------------------------------------------------------
 	// Other methods
 	//--------------------------------------------------------------------------------
@@ -501,4 +577,32 @@ public final class XmlUtils {
 			return "ENTITY_DECLARATION";
 		return "UNKNOWN";
 	}
+
+	/**
+	 * Shortcut for calling <code>URLEncoder.<jsm>encode</jsm>(o.toString(), <js>"UTF-8"</js>)</code>.
+	 *
+	 * @param o The object to encode.
+	 * @return The URL encoded string, or <jk>null</jk> if the object was null.
+	 */
+	public static String urlEncode(Object o) {
+		try {
+			if (o != null)
+				return URLEncoder.encode(o.toString(), "UTF-8");
+		} catch (UnsupportedEncodingException e) {}
+		return null;
+	}
+
+	/**
+	 * Shortcut for calling <code>URLEncoder.<jsm>decode</jsm>(o.toString(), <js>"UTF-8"</js>)</code>.
+	 *
+	 * @param s The string to decode.
+	 * @return The decoded string, or <jk>null</jk> if the string was null.
+	 */
+	public static String urlDecode(String s) {
+		try {
+			if (s != null)
+				return URLDecoder.decode(s, "UTF-8");
+		} catch (UnsupportedEncodingException e) {}
+		return null;
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/java/org/apache/juneau/xml/XmlWriter.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlWriter.java b/juneau-core/src/main/java/org/apache/juneau/xml/XmlWriter.java
index 6b8caeb..d1ed78e 100644
--- a/juneau-core/src/main/java/org/apache/juneau/xml/XmlWriter.java
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlWriter.java
@@ -38,15 +38,12 @@ public class XmlWriter extends SerializerWriter {
 	 * @param useWhitespace If <jk>true</jk> XML elements will be indented.
 	 * @param trimStrings If <jk>true</jk>, strings should be trimmed before they're serialized.
 	 * @param quoteChar The quote character to use for attributes.  Should be <js>'\''</js> or <js>'"'</js>.
-	 * @param relativeUriBase The base (e.g. <js>https://localhost:9443/contextPath"</js>) for relative URIs (e.g. <js>"my/path"</js>).
-	 * @param absolutePathUriBase The base (e.g. <js>https://localhost:9443"</js>) for relative URIs with absolute paths (e.g. <js>"/contextPath/my/path"</js>).
-	 * @param uriContext The URI context.
-	 * 	Identifies the current request URI used for resolution of URIs to absolute or root-relative form.
+	 * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form.
 	 * @param enableNs Flag to indicate if XML namespaces are enabled.
 	 * @param defaultNamespace The default namespace if XML namespaces are enabled.
 	 */
-	public XmlWriter(Writer out, boolean useWhitespace, boolean trimStrings, char quoteChar, String relativeUriBase, String absolutePathUriBase, UriContext uriContext, boolean enableNs, Namespace defaultNamespace) {
-		super(out, useWhitespace, trimStrings, quoteChar, relativeUriBase, absolutePathUriBase, uriContext);
+	public XmlWriter(Writer out, boolean useWhitespace, boolean trimStrings, char quoteChar, UriResolver uriResolver, boolean enableNs, Namespace defaultNamespace) {
+		super(out, useWhitespace, trimStrings, quoteChar, uriResolver);
 		this.enableNs = enableNs;
 		this.defaultNsPrefix = defaultNamespace == null ? null : defaultNamespace.name;
 	}
@@ -404,17 +401,12 @@ public class XmlWriter extends SerializerWriter {
 	 * @param ns The namespace.  Can be <jk>null</jk>.
 	 * @param name The attribute name.
 	 * @param value The attribute value.
-	 * @param needsEncoding If <jk>true</jk>, attribute name will be encoded.
+	 * @param valNeedsEncoding If <jk>true</jk>, attribute name will be encoded.
 	 * @return This object (for method chaining).
 	 * @throws IOException If a problem occurred.
 	 */
-	public XmlWriter attr(String ns, String name, Object value, boolean needsEncoding) throws IOException {
-		oAttr(ns, name).q();
-		if (needsEncoding)
-			encodeAttr(value);
-		else
-			append(value);
-		return q();
+	public XmlWriter attr(String ns, String name, Object value, boolean valNeedsEncoding) throws IOException {
+		return oAttr(ns, name).q().attrValue(value, valNeedsEncoding).q();
 	}
 
 	/**
@@ -422,12 +414,12 @@ public class XmlWriter extends SerializerWriter {
 	 *
 	 * @param name The attribute name.
 	 * @param value The attribute value.
-	 * @param needsEncoding If <jk>true</jk>, attribute name will be encoded.
+	 * @param valNeedsEncoding If <jk>true</jk>, attribute name will be encoded.
 	 * @return This object (for method chaining).
 	 * @throws IOException If a problem occurred.
 	 */
-	public XmlWriter attr(String name, Object value, boolean needsEncoding) throws IOException {
-		return attr(null, name, value, needsEncoding);
+	public XmlWriter attr(String name, Object value, boolean valNeedsEncoding) throws IOException {
+		return attr(null, name, value, valNeedsEncoding);
 	}
 
 	/**
@@ -440,11 +432,11 @@ public class XmlWriter extends SerializerWriter {
 	 * @throws IOException If a problem occurred.
 	 */
 	public XmlWriter attr(String ns, String name, Object value) throws IOException {
-		return oAttr(ns, name).q().append(value).q();
+		return oAttr(ns, name).q().attrValue(value, false).q();
 	}
 
 	/**
-	 * Same as {@link #attr(String, Object, boolean)}, except pass in a {@link Namespace} object for the namespace.
+	 * Same as {@link #attr(String, String, Object)}, except pass in a {@link Namespace} object for the namespace.
 	 *
 	 * @param ns The namespace.  Can be <jk>null</jk>.
 	 * @param name The attribute name.
@@ -453,7 +445,7 @@ public class XmlWriter extends SerializerWriter {
 	 * @throws IOException If a problem occurred.
 	 */
 	public XmlWriter attr(Namespace ns, String name, Object value) throws IOException {
-		return oAttr(ns == null ? null : ns.name, name).q().append(value).q();
+		return oAttr(ns == null ? null : ns.name, name).q().attrValue(value, false).q();
 	}
 
 	/**
@@ -507,8 +499,7 @@ public class XmlWriter extends SerializerWriter {
 	 * @throws IOException If a problem occurred.
 	 */
 	public XmlWriter attrUri(Namespace ns, String name, Object value) throws IOException {
-		oAttr(ns, name).q().appendUri(value).q();
-		return this;
+		return attr(ns, name, uriResolver.resolve(value));
 	}
 
 	/**
@@ -521,8 +512,7 @@ public class XmlWriter extends SerializerWriter {
 	 * @throws IOException If a problem occurred.
 	 */
 	public XmlWriter attrUri(String ns, String name, Object value) throws IOException {
-		oAttr(ns, name).q().appendUri(value).q();
-		return this;
+		return attr(ns, name, uriResolver.resolve(value), true);
 	}
 
 	/**
@@ -551,14 +541,22 @@ public class XmlWriter extends SerializerWriter {
 	}
 
 	/**
-	 * Serializes and encodes the specified object as valid XML attribute name.
+	 * Same as {@link #text(Object)} but treats the value as a URL to resolved then serialized.
 	 *
 	 * @param o The object being serialized.
 	 * @return This object (for method chaining).
-	 * @throws IOException If a problem occurred.
+	 * @throws IOException
 	 */
-	public XmlWriter encodeAttr(Object o) throws IOException {
-		XmlUtils.encodeAttr(out, o);
+	public XmlWriter textUri(Object o) throws IOException {
+		text(uriResolver.resolve(o), false);
+		return this;
+	}
+
+	private XmlWriter attrValue(Object o, boolean needsEncoding) throws IOException {
+		if (needsEncoding)
+			XmlUtils.encodeAttrValue(out, o, this.trimStrings);
+		else
+			append(o.toString());
 		return this;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-core/src/main/javadoc/overview.html
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/javadoc/overview.html b/juneau-core/src/main/javadoc/overview.html
index fe24402..64714b6 100644
--- a/juneau-core/src/main/javadoc/overview.html
+++ b/juneau-core/src/main/javadoc/overview.html
@@ -2187,14 +2187,30 @@
 	<p class='bcode'>
 	<ja>@RestResource</ja>(
 		path=<js>"/systemProperties"</js>,
+		
+		<jc>// Title and description that show up on HTML rendition page.
+		// Also used in Swagger doc.</jc>
 		title=<js>"System properties resource"</js>,
 		description=<js>"REST interface for performing CRUD operations on system properties."</js>,
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js>,
+		
+		<jc>// Links on the HTML rendition page.
+		// "request:/..." URIs are relative to the request URI.
+		// "servlet:/..." URIs are relative to the servlet URI.</jc>
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js>,
+		
+		<jc>// Properties that get applied to all serializers and parsers.</jc>
 		properties={
+			<jc>// Use single quotes.</jc>
 			<ja>@Property</ja>(name=<jsf>SERIALIZER_quoteChar</jsf>, value=<js>"'"</js>)
 		},
+		
+		<jc>// Our stylesheet for the HTML rendition.</jc>
 		stylesheet=<js>"styles/devops.css"</js>,
+		
+		<jc>// Support GZIP encoding on Accept-Encoding header.</jc>
 		encoders=GzipEncoder.<jk>class</jk>,
+
+		<jc>// Swagger info.</jc>
 		contact=<js>"{name:'John Smith',email:'john@smith.com'}"</js>,
 		license=<js>"{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'}"</js>,
 		version=<js>"2.0"</js>,
@@ -3076,7 +3092,7 @@
 	<ja>@RestResource</ja>( 
 		messages=<js>"nls/HelloWorldResource"</js>, 
 		path=<js>"/helloWorld"</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js>
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js>
 	) 
 	<jk>public class</jk> HelloWorldResource <jk>extends</jk> Resource { 
 		<jk>private static final long</jk> <jsf>serialVersionUID</jsf> = 1L; 
@@ -3151,7 +3167,7 @@
 	<ja>@RestResource</ja>( 
 		path=<js>"/methodExample"</js>, 
 		messages=<js>"nls/MethodExampleResource"</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js>
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js>
 	) 
 	<jk>public class</jk> MethodExampleResource <jk>extends</jk> Resource { 
 		<jk>private static final long</jk> <jsf>serialVersionUID</jsf> = 1L; 
@@ -3620,7 +3636,7 @@
 	<ja>@RestResource</ja>(
 		path=<js>"/echo"</js>,
 		messages=<js>"nls/RequestEchoResource"</js>,
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js>,
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js>,
 		properties={
 			<ja>@Property</ja>(name=<jsf>SERIALIZER_maxDepth</jsf>, value=<js>"10"</js>),
 			<ja>@Property</ja>(name=<jsf>SERIALIZER_detectRecursions</jsf>, value=<js>"true"</js>)
@@ -4075,18 +4091,42 @@
 	<ja>@RestResource</ja>( 
 		path=<js>"/addressBook"</js>, 
 		messages=<js>"nls/AddressBookResource"</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js>, 
-		properties={ 
-			<ja>@Property</ja>(name=<jsf>REST_allowMethodParam</jsf>, value=<js>"*"</js>), 
-			<ja>@Property</ja>(name=<jsf>HTML_uriAnchorText</jsf>, value=<jsf>TO_STRING</jsf>), 
-			<ja>@Property</ja>(name=<jsf>SERIALIZER_quoteChar</jsf>, value=<js>"'"</js>), 
-			<ja>@Property</ja>(name=<jsf>RDF_rdfxml_tab</jsf>, value=<js>"5"</js>), 
-			<ja>@Property</ja>(name=<jsf>RDF_addRootProperty</jsf>, value=<js>"true"</js>), 
-			<jc>// Resolve all relative URIs so that they're relative to this servlet!</jc> 
-			<ja>@Property</ja>(name=<jsf>SERIALIZER_relativeUriBase</jsf>, value=<js>"$R{servletURI}"</js>), 
-		}, 
+
+		<jc>// Links on the HTML rendition page.
+		// "request:/..." URIs are relative to the request URI.
+		// "servlet:/..." URIs are relative to the servlet URI.
+		// "$C{...}" variables are pulled from the config file.</jc>
+		pageLinks=<js>"{up:'request:/..', options:'servlet:/?method=OPTIONS', source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java'}"</js>,
+
+		<jc>// Properties that get applied to all serializers and parsers.</jc>
+		properties={
+			
+			<jc>// Allow INIT as a method parameter.</jc>
+			<ja>@Property</ja>(name=<jsf>REST_allowMethodParam</jsf>, value=<js>"*"</js>),
+	
+			<jc>// Use single quotes.</jc>
+			<ja>@Property</ja>(name=<jsf>SERIALIZER_quoteChar</jsf>, value=<js>"'"</js>),
+			
+			<jc>// Make RDF/XML readable.</jc>
+			<ja>@Property</ja>(name=<jsf>RDF_rdfxml_tab</jsf>, value=<js>"5"</js>),
+			
+			<jc>// Make RDF parsable by adding a root node.</jc>
+			<ja>@Property</ja>(name=<jsf>RDF_addRootProperty</jsf>, value=<js>"true"</js>),
+			
+			<jc>// Make URIs absolute so that we can easily reference them on the client side.</jc>
+			<ja>@Property</ja>(name=<jsf>SERIALIZER_uriResolution</jsf>, value=<js>"ABSOLUTE"</js>)
+		
+			<jc>// Make the anchor text on URLs be just the path relative to the servlet.</jc>
+			<ja>@Property</ja>(name=<jsf>HTML_uriAnchorText</jsf>, value=<js>"SERVLET_RELATIVE"</js>)
+		},
+		
+		<jc>// Our stylesheet for the HTML rendition.</jc>
 		stylesheet=<js>"styles/devops.css"</js>,
+		
+		<jc>// Support GZIP encoding on Accept-Encoding header.</jc>
 		encoders=GzipEncoder.<jk>class</jk>,
+		
+		<jc>// Swagger info.</jc>
 		contact=<js>"{name:'John Smith',email:'john@smith.com'}"</js>,
 		license=<js>"{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'}"</js>,
 		version=<js>"2.0"</js>,
@@ -4105,7 +4145,7 @@
 		
 			<jk>try</jk> { 
 				<jc>// Create the address book</jc> 
-				<jf>addressBook</jf> = <jk>new</jk> AddressBook(java.net.URI.create(<js>""</js>)); 
+				<jf>addressBook</jf> = <jk>new</jk> AddressBook(java.net.URI.create(<js>"servlet:/"</js>)); 
 				
 				<jc>// Add some people to our address book by default</jc> 
 				<jf>addressBook</jf>.createPerson( 
@@ -4724,7 +4764,7 @@
 		messages=<js>"nls/SampleRemoteableServlet"</js>, 
 		title=<js>"Remoteable Service Proxy API"</js>, 
 		description=<js>"Sample class showing how to use remoteable proxies. The list below are exposed services that can be retrieved using RestClient.getProxyInterface(Class)."</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js>, 
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js>, 
 		properties={ 
 			<jc>// Allow us to use method=POST from a browser.</jc> 
 			<ja>@Property</ja>(name=<jsf>REST_allowMethodParam</jsf>, value=<js>"*"</js>) 
@@ -4827,7 +4867,7 @@
 	<ja>@RestResource</ja>( 
 		path=<js>"/tempDir"</js>, 
 		messages=<js>"nls/TempDirResource"</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS',upload:'upload'}"</js>,
+		pageLinks=<js>"{up:'request:/..', options:'servlet:/?method=OPTIONS', upload:'servlet:/upload'}"</js>,
 		properties={ 
 			<ja>@Property</ja>(name=<js>"DirectoryResource.rootDir"</js>, value=<js>"$S{java.io.tmpdir}"</js>), 
 			<ja>@Property</ja>(name=<js>"DirectoryResource.allowViews"</js>, value=<js>"true"</js>), 
@@ -4945,7 +4985,7 @@
 	<ja>@RestResource</ja>( 
 		path=<js>"/atom"</js>, 
 		messages=<js>"nls/AtomFeedResource"</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js>,
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js>,
 		properties={ 
 			<ja>@Property</ja>(name=<jsf>SERIALIZER_quoteChar</jsf>, value=<js>"'"</js>), 
 			<ja>@Property</ja>(name=<jsf>RDF_rdfxml_tab</jsf>, value=<js>"5"</js>), 
@@ -5053,7 +5093,7 @@
 	<ja>@RestResource</ja>( 
 		path=<js>"/docker"</js>, 
 		title=<js>"Sample Docker resource"</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js> 
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js> 
 	) 
 	<jk>public class</jk> DockerRegistryResource <jk>extends</jk> Resource { 
 		<jk>private static final long</jk> <jsf>serialVersionUID</jsf> = 1L; 
@@ -5134,7 +5174,7 @@
 		messages=<js>"nls/TumblrParserResource"</js>, 
 		title=<js>"Tumblr parser service"</js>, 
 		description=<js>"Specify a URL to a Tumblr blog and parse the results."</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js> 
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js> 
 	) 
 	<jk>public class</jk> TumblrParserResource <jk>extends</jk> Resource { 
 		<jk>private static final long</jk> <jsf>serialVersionUID</jsf> = 1L; 
@@ -5355,7 +5395,7 @@
 		path=<js>"/jsonSchema"</js>, 
 		messages=<js>"nls/JsonSchemaResource"</js>, 
 		title=<js>"Sample JSON-Schema document"</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js> 
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js> 
 	) 
 	<jk>public class</jk> JsonSchemaResource <jk>extends</jk> ResourceJena { 
 		<jk>private static final long</jk> <jsf>serialVersionUID</jsf> = 1L; 
@@ -5436,7 +5476,7 @@
 		messages=<js>"nls/SqlQueryResource"</js>, 
 		title=<js>"SQL query service"</js>, 
 		description=<js>"Executes queries against the local derby '$C{SqlQueryResource/connectionUrl}' database"</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS'}"</js> 
+		pageLinks=<js>"{up:'request:/..',options:'servlet:/?method=OPTIONS'}"</js> 
 	) 
 	<jk>public class</jk> SqlQueryResource <jk>extends</jk> Resource { 
 		<jk>private static final long</jk> <jsf>serialVersionUID</jsf> = 1L; 
@@ -5623,7 +5663,7 @@
 		path=<js>"/config"</js>, 
 		title=<js>"Configuration"</js>, 
 		description=<js>"Contents of configuration file."</js>, 
-		pageLinks=<js>"{up:'$R{requestParentURI}',options:'?method=OPTIONS',edit:'edit'}"</js>
+		pageLinks=<js>"{up:'request:/..', options:'servlet:/?method=OPTIONS', edit:'servlet:/edit'}"</js>
 	) 
 	<jk>public class</jk> ConfigResource <jk>extends</jk> Resource { 
 		<jk>private static final long</jk> <jsf>serialVersionUID</jsf> = 1L; 
@@ -6150,6 +6190,39 @@
 					<li>{@link org.apache.juneau.remoteable.Header#serializer} 
 					<li>{@link org.apache.juneau.remoteable.HeaderIfNE#serializer} 
 				</ul>
+			<li>Across-the-board improvements to the URI-resolution support (i.e. how URIs get serialized).
+				<ul>
+					<li>New support for resolving URIs with the following newly-recognized protocols:
+						<ul>
+							<li><js>"context:/..."</js> - Relative to context-root of the application.
+							<li><js>"servlet:/..."</js> - Relative to the servlet URI.
+							<li><js>"request:/..."</js> - Relative to the request URI.
+						</ul>
+						For example, currently we define HTML page links using variables and servlet-relative URIs...
+						<p class='bcode'>
+	pageLinks=<js>"{up:'$R{requestParentURI}', options:'?method=OPTIONS', upload:'upload'}"</js>
+						</p>
+						With these new protocols, we can define them like so:
+						<p class='bcode'>
+	pageLinks=<js>"{top:'context:/', up:'request:/..' ,options:'servlet:/?method=OPTIONS', upload:'servlet:/upload'}"</js>
+						</p>
+						The old method of using variables and servlet-relative URIs will still be supported, but using 
+						these new protocols should (hopefully) be easier to understand.
+						<br>
+						These protocols work on all serialized URL and URI objects, as well as classes and properties 
+						annotated with {@link org.apache.juneau.annotation.URI @URI}.
+					<li>New classes:
+						<ul>
+							<li>{@link org.apache.juneau.UriContext}
+							<li>{@link org.apache.juneau.UriRelativity}
+							<li>{@link org.apache.juneau.UriResolution}
+							<li>{@link org.apache.juneau.UriResolver}
+						</ul>
+					<li>New configuration properties:
+						<li>{@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_uriContext}
+						<li>{@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_uriRelativity}
+						<li>{@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_uriResolution}
+				</ul>
 		</ul>
 
 		<h6 class='topic'>org.apache.juneau.rest</h6>
@@ -7839,8 +7912,8 @@
 		<ul class='spaced-list'>
 			<li>New properties in {@link org.apache.juneau.serializer.SerializerContext}:
 				<ol>
-					<li>{@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_relativeUriBase}
-					<li>{@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_absolutePathUriBase}
+					<li><code><del>SerializerContext.SERIALIZER_relativeUriBase</del></code>
+					<li><code><del>SerializerContext.SERIALIZER_absolutePathUriBase</del></code>
 				</ol>
 				These replace the <code>SERIALIZER_uriAuthority</code> and <code>SERIALIZER_uriContext</code> properties.
 			</li>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/Person.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/Person.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/Person.java
index 1d8c73c..e95806a 100755
--- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/Person.java
+++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/Person.java
@@ -32,7 +32,7 @@ public class Person {
 
 	// Bean properties
 	@Rdf(beanUri=true) public URI uri;
-	public URI addressBookUri;
+	private URI addressBookUri;
 	public int id;
 	public String name;
 	@BeanProperty(swap=CalendarSwap.DateMedium.class) public Calendar birthDate;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/AtomFeedResource.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/AtomFeedResource.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/AtomFeedResource.java
index ff453ae..49fc847 100644
--- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/AtomFeedResource.java
+++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/AtomFeedResource.java
@@ -31,7 +31,7 @@ import org.apache.juneau.rest.annotation.*;
 	path="/atom",
 	title="Sample ATOM feed resource",
 	description="Sample resource that shows how to render ATOM feeds",
-	pageLinks="{up:'$R{requestParentURI}',options:'?method=OPTIONS',source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/AtomFeedResource.java'}",
+	pageLinks="{up:'request:/..',options:'servlet:/?method=OPTIONS',source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/AtomFeedResource.java'}",
 	properties={
 		@Property(name=SERIALIZER_quoteChar, value="'"),
 		@Property(name=RDF_rdfxml_tab, value="5"),

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DirectoryResource.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DirectoryResource.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DirectoryResource.java
index 9c3919f..753fcf7 100644
--- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DirectoryResource.java
+++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DirectoryResource.java
@@ -35,7 +35,7 @@ import org.apache.juneau.utils.*;
  */
 @RestResource(
 	messages="nls/DirectoryResource",
-	pageLinks="{up:'$R{requestParentURI}',options:'?method=OPTIONS',source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/DirectoryResource.java'}",
+	pageLinks="{up:'request:/..',options:'servlet:/?method=OPTIONS',source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/DirectoryResource.java'}",
 	properties={
 		@Property(name=HTML_uriAnchorText, value=PROPERTY_NAME),
 		@Property(name=REST_allowMethodParam, value="*"),

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c4952d2c/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DockerRegistryResource.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DockerRegistryResource.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DockerRegistryResource.java
index 61192f0..1b021f0 100644
--- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DockerRegistryResource.java
+++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DockerRegistryResource.java
@@ -27,7 +27,7 @@ import org.apache.juneau.rest.labels.*;
 @RestResource(
 	path="/docker",
 	title="Sample Docker resource",
-	pageLinks="{up:'$R{requestParentURI}',options:'?method=OPTIONS',source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/DockerRegistryResource.java'}"
+	pageLinks="{up:'request:/..',options:'servlet:/?method=OPTIONS',source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/DockerRegistryResource.java'}"
 )
 public class DockerRegistryResource extends Resource {
 	private static final long serialVersionUID = 1L;