HtmlRender support.

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

Branch: refs/heads/master
Commit: e1a505668d7aa4d2aa816f697a8203dfbc4bd8c8
Parents: 9b04bb9
Author: JamesBognar <[email protected]>
Authored: Sun May 21 01:17:14 2017 -0400
Committer: JamesBognar <[email protected]>
Committed: Sun May 21 01:17:14 2017 -0400

----------------------------------------------------------------------
 .../org/apache/juneau/jena/RdfSerializer.java   |   4 +-
 .../juneau/jena/RdfSerializerSession.java       |  14 +-
 .../apache/juneau/utils/StringUtilsTest.java    |  28 +
 .../utils/UriContextResolutionComboTest.java    | 630 +++++++++++++++++++
 .../juneau/utils/UriContextUriComboTest.java    | 234 +++++++
 .../org/apache/juneau/xml/XmlContentTest.java   |   8 +-
 .../main/java/org/apache/juneau/BeanMap.java    |  11 +
 .../main/java/org/apache/juneau/UriContext.java | 421 +++++++++++++
 .../java/org/apache/juneau/UriRelativity.java   |  29 +
 .../java/org/apache/juneau/UriResolution.java   |  34 +
 .../apache/juneau/csv/CsvSerializerSession.java |  14 +-
 .../juneau/html/HtmlBeanPropertyMeta.java       |  78 ++-
 .../apache/juneau/html/HtmlDocSerializer.java   |   4 +-
 .../juneau/html/HtmlDocSerializerSession.java   |  16 +-
 .../java/org/apache/juneau/html/HtmlRender.java | 149 +++++
 .../juneau/html/HtmlSchemaDocSerializer.java    |   4 +-
 .../org/apache/juneau/html/HtmlSerializer.java  |  38 +-
 .../juneau/html/HtmlSerializerSession.java      |  16 +-
 .../java/org/apache/juneau/html/HtmlWriter.java |   7 +-
 .../apache/juneau/html/SimpleHtmlWriter.java    |   2 +-
 .../org/apache/juneau/html/annotation/Html.java |  35 ++
 .../juneau/html/doc-files/HtmlRender_1.png      | Bin 0 -> 60161 bytes
 .../org/apache/juneau/internal/StringUtils.java | 102 +++
 .../juneau/json/JsonSchemaSerializer.java       |   4 +-
 .../org/apache/juneau/json/JsonSerializer.java  |   4 +-
 .../juneau/json/JsonSerializerSession.java      |  16 +-
 .../java/org/apache/juneau/json/JsonWriter.java |   7 +-
 .../juneau/msgpack/MsgPackSerializer.java       |   4 +-
 .../msgpack/MsgPackSerializerSession.java       |  14 +-
 .../apache/juneau/serializer/Serializer.java    |  12 +-
 .../juneau/serializer/SerializerSession.java    |  45 +-
 .../juneau/serializer/SerializerWriter.java     |  12 +-
 .../juneau/serializer/WriterSerializer.java     |   2 +-
 .../org/apache/juneau/uon/UonSerializer.java    |   4 +-
 .../apache/juneau/uon/UonSerializerSession.java |  16 +-
 .../java/org/apache/juneau/uon/UonWriter.java   |   7 +-
 .../urlencoding/UrlEncodingSerializer.java      |   6 +-
 .../UrlEncodingSerializerSession.java           |  14 +-
 .../apache/juneau/xml/XmlSchemaSerializer.java  |   8 +-
 .../org/apache/juneau/xml/XmlSerializer.java    |   4 +-
 .../apache/juneau/xml/XmlSerializerSession.java |  16 +-
 .../java/org/apache/juneau/xml/XmlWriter.java   |   7 +-
 .../src/main/javadoc/doc-files/HtmlRender_1.png | Bin 0 -> 60161 bytes
 juneau-core/src/main/javadoc/overview.html      |   3 +
 .../juneau/examples/rest/FileSpaceResource.java | 148 +++++
 .../juneau/examples/rest/RootResources.java     |   1 +
 .../apache/juneau/examples/rest/htdocs/ok.png   | Bin 0 -> 455 bytes
 .../juneau/examples/rest/htdocs/severe.png      | Bin 0 -> 335 bytes
 .../juneau/examples/rest/htdocs/warning.png     | Bin 0 -> 444 bytes
 .../juneau/rest/client/RestClientBuilder.java   |   2 +-
 .../apache/juneau/rest/jaxrs/BaseProvider.java  |   4 +-
 .../juneau/rest/test/HtmlPropertiesTest.java    |   1 -
 .../org/apache/juneau/rest/RestRequest.java     |  17 +
 .../juneau/rest/response/DefaultHandler.java    |   4 +-
 54 files changed, 2112 insertions(+), 148 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializer.java 
b/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializer.java
index 37b7d2c..734f38a 100644
--- a/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializer.java
+++ b/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializer.java
@@ -450,7 +450,7 @@ public class RdfSerializer extends WriterSerializer {
        
//--------------------------------------------------------------------------------
 
        @Override /* Serializer */
-       public RdfSerializerSession createSession(Object output, ObjectMap op, 
Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) {
-               return new RdfSerializerSession(ctx, op, output, javaMethod, 
locale, timeZone, mediaType);
+       public RdfSerializerSession createSession(Object output, ObjectMap op, 
Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType, 
UriContext uriContext) {
+               return new RdfSerializerSession(ctx, op, output, javaMethod, 
locale, timeZone, mediaType, uriContext);
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java
 
b/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java
index 199f144..55060ef 100644
--- 
a/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java
+++ 
b/juneau-core-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java
@@ -54,19 +54,21 @@ public final class RdfSerializerSession extends 
SerializerSession {
         * Create a new session using properties specified in the context.
         *
         * @param ctx The context creating this session object.
-        * The context contains all the configuration settings for this object.
+        *      The context contains all the configuration settings for this 
object.
         * @param output The output object.  See {@link 
JsonSerializerSession#getWriter()} for valid class types.
         * @param op The override properties.
-        * These override any context properties defined in the context.
+        *      These override any context properties defined in the context.
         * @param javaMethod The java method that called this serializer, 
usually the method in a REST servlet.
         * @param locale The session locale.
-        * If <jk>null</jk>, then the locale defined on the context is used.
+        *      If <jk>null</jk>, then the locale defined on the context is 
used.
         * @param timeZone The session timezone.
-        * If <jk>null</jk>, then the timezone defined on the context is used.
+        *      If <jk>null</jk>, then the timezone defined on the context is 
used.
         * @param mediaType The session media type (e.g. 
<js>"application/json"</js>).
+        * @param uriContext The URI context.
+        *      Identifies the current request URI used for resolution of URIs 
to absolute or root-relative form. 
         */
-       protected RdfSerializerSession(RdfSerializerContext ctx, ObjectMap op, 
Object output, Method javaMethod, Locale locale, TimeZone timeZone, MediaType 
mediaType) {
-               super(ctx, op, output, javaMethod, locale, timeZone, mediaType);
+       protected RdfSerializerSession(RdfSerializerContext ctx, ObjectMap op, 
Object output, Method javaMethod, Locale locale, TimeZone timeZone, MediaType 
mediaType, UriContext uriContext) {
+               super(ctx, op, output, javaMethod, locale, timeZone, mediaType, 
uriContext);
                ObjectMap jenaSettings = new ObjectMap();
                jenaSettings.put("rdfXml.tab", isUseWhitespace() ? 2 : 0);
                jenaSettings.put("rdfXml.attributeQuoteChar", 
Character.toString(getQuoteChar()));

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java 
b/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
index f74d75e..c7c3052 100755
--- 
a/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
+++ 
b/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
@@ -717,4 +717,32 @@ public class StringUtilsTest {
                assertObjectEquals("{'a=':'1'}", splitMap("a\\==1", ',', '=', 
true));
                assertObjectEquals("{'a\\\\':'1'}", splitMap("a\\\\=1", ',', 
'=', true));
        }
+       
+       
//====================================================================================================
+       // isAbsoluteUri(String)
+       
//====================================================================================================
+       @Test
+       public void testIsAbsoluteUri() {
+               assertFalse(isAbsoluteUri(null));
+               assertFalse(isAbsoluteUri(""));
+               assertTrue(isAbsoluteUri("http://foo";));
+               assertTrue(isAbsoluteUri("x://x"));
+               assertFalse(isAbsoluteUri("xX://x"));
+               assertFalse(isAbsoluteUri("x ://x"));
+               assertFalse(isAbsoluteUri("x: //x"));
+               assertFalse(isAbsoluteUri("x:/ /x"));
+               assertFalse(isAbsoluteUri("x:x//x"));
+               assertFalse(isAbsoluteUri("x:/x/x"));
+       }
+
+       
//====================================================================================================
+       // getAuthorityUri(String)
+       
//====================================================================================================
+       @Test
+       public void testGetAuthorityUri() {
+               assertEquals("http://foo";, getAuthorityUri("http://foo";));
+               assertEquals("http://foo:123";, 
getAuthorityUri("http://foo:123";));
+               assertEquals("http://foo:123";, 
getAuthorityUri("http://foo:123/";));
+               assertEquals("http://foo:123";, 
getAuthorityUri("http://foo:123/bar";));
+       }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextResolutionComboTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextResolutionComboTest.java
 
b/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextResolutionComboTest.java
new file mode 100644
index 0000000..e69a635
--- /dev/null
+++ 
b/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextResolutionComboTest.java
@@ -0,0 +1,630 @@
+// 
***************************************************************************************************************************
+// * 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.utils;
+
+import static org.apache.juneau.TestUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.*;
+import org.junit.runner.*;
+import org.junit.runners.*;
+
+/**
+ * Verifies that the resolveUri() methods in UriContext work correctly.
+ */
+@RunWith(Parameterized.class)
+public class UriContextResolutionComboTest {
+
+       @Parameterized.Parameters
+       public static Collection<Object[]> getInput() {
+               return Arrays.asList(new Object[][] {
+
+                       // Happy cases - All URL parts known.
+                       {
+                               input(
+                                       "Happy-1",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-2",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-3",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "/foobar",
+                                       "http://host:port/foobar";,
+                                       "/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-4",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "/",
+                                       "http://host:port";,
+                                       "/"
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-5",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "foobar",
+                                       
"http://host:port/context/resource/foobar";,
+                                       "/context/resource/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-6",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "",
+                                       
"http://host:port/context/resource/path";,
+                                       "/context/resource/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-7",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "context:/foo",
+                                       "http://host:port/context/foo";,
+                                       "/context/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-8",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "context:/",
+                                       "http://host:port/context";,
+                                       "/context"
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-9",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "servlet:/foo",
+                                       "http://host:port/context/resource/foo";,
+                                       "/context/resource/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-10",
+                                       
"http://host:port","/context","/resource","/path";,
+                                       "servlet:/",
+                                       "http://host:port/context/resource";,
+                                       "/context/resource"
+                               )
+                       },
+                       
+                       // Multiple context and resource parts
+                       {
+                               input(
+                                       "MultiContextResource-1",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";
+                               )
+                       },
+                       {
+                               input(
+                                       "MultiContextResource-2",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";
+                               )
+                       },
+                       {
+                               input(
+                                       "MultiContextResource-3",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "/foobar",
+                                       "http://host:port/foobar";,
+                                       "/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "MultiContextResource-4",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "/",
+                                       "http://host:port";,
+                                       "/"
+                               )
+                       },
+                       {
+                               input(
+                                       "MultiContextResource-5",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "foobar",
+                                       
"http://host:port/c1/c2/r1/r2/p1/foobar";,
+                                       "/c1/c2/r1/r2/p1/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "MultiContextResource-6",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "",
+                                       "http://host:port/c1/c2/r1/r2/p1/p2";,
+                                       "/c1/c2/r1/r2/p1/p2"
+                               )
+                       },
+                       {
+                               input(
+                                       "MultiContextResource-7",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "context:/foo",
+                                       "http://host:port/c1/c2/foo";,
+                                       "/c1/c2/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "MultiContextResource-8",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "context:/",
+                                       "http://host:port/c1/c2";,
+                                       "/c1/c2"
+                               )
+                       },
+                       {
+                               input(
+                                       "MultiContextResource-9",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "servlet:/foo",
+                                       "http://host:port/c1/c2/r1/r2/foo";,
+                                       "/c1/c2/r1/r2/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "MultiContextResource-10",
+                                       
"http://host:port","/c1/c2","/r1/r2","/p1/p2";,
+                                       "servlet:/",
+                                       "http://host:port/c1/c2/r1/r2";,
+                                       "/c1/c2/r1/r2"
+                               )
+                       },
+                       
+                       // No authority given
+                       {
+                               input(
+                                       "NoAuthority-1",
+                                       "","/context","/resource","/path",
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-2",
+                                       "","/context","/resource","/path",
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-3",
+                                       "","/context","/resource","/path",
+                                       "/foobar",
+                                       "/foobar",
+                                       "/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-4",
+                                       "","/context","/resource","/path",
+                                       "/",
+                                       "/",
+                                       "/"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-5",
+                                       "","/context","/resource","/path",
+                                       "foobar",
+                                       "/context/resource/foobar",
+                                       "/context/resource/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-6",
+                                       "","/context","/resource","/path",
+                                       "",
+                                       "/context/resource/path",
+                                       "/context/resource/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-7",
+                                       "","/context","/resource","/path",
+                                       "context:/foo",
+                                       "/context/foo",
+                                       "/context/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-8",
+                                       "","/context","/resource","/path",
+                                       "context:/",
+                                       "/context",
+                                       "/context"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-9",
+                                       "","/context","/resource","/path",
+                                       "servlet:/foo",
+                                       "/context/resource/foo",
+                                       "/context/resource/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-10",
+                                       "","/context","/resource","/path",
+                                       "servlet:/",
+                                       "/context/resource",
+                                       "/context/resource"
+                               )
+                       },
+                       
+                       // No authority or context given
+                       {
+                               input(
+                                       "NoAuthorityOrContext-1",
+                                       "","","/resource","/path",
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContext-2",
+                                       "","","/resource","/path",
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContext-3",
+                                       "","","/resource","/path",
+                                       "/foobar",
+                                       "/foobar",
+                                       "/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContext-4",
+                                       "","","/resource","/path",
+                                       "/",
+                                       "/",
+                                       "/"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContext-5",
+                                       "","","/resource","/path",
+                                       "foobar",
+                                       "/resource/foobar",
+                                       "/resource/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContext-6",
+                                       "","","/resource","/path",
+                                       "",
+                                       "/resource/path",
+                                       "/resource/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContext-7",
+                                       "","","/resource","/path",
+                                       "context:/foo",
+                                       "/foo",
+                                       "/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContext-8",
+                                       "","","/resource","/path",
+                                       "context:/",
+                                       "/",
+                                       "/"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContext-9",
+                                       "","","/resource","/path",
+                                       "servlet:/foo",
+                                       "/resource/foo",
+                                       "/resource/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContext-10",
+                                       "","","/resource","/path",
+                                       "servlet:/",
+                                       "/resource",
+                                       "/resource"
+                               )
+                       },
+
+                       // No authority or context or resource given
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-1",
+                                       "","","","/path",
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-2",
+                                       "","","","/path",
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-3",
+                                       "","","","/path",
+                                       "/foobar",
+                                       "/foobar",
+                                       "/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-4",
+                                       "","","","/path",
+                                       "/",
+                                       "/",
+                                       "/"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-5",
+                                       "","","","/path",
+                                       "foobar",
+                                       "/foobar",
+                                       "/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-6",
+                                       "","","","/path",
+                                       "",
+                                       "/path",
+                                       "/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-7",
+                                       "","","","/path",
+                                       "context:/foo",
+                                       "/foo",
+                                       "/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-8",
+                                       "","","","/path",
+                                       "context:/",
+                                       "/",
+                                       "/"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-9",
+                                       "","","","/path",
+                                       "servlet:/foo",
+                                       "/foo",
+                                       "/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityOrContextOrResource-10",
+                                       "","","","/path",
+                                       "servlet:/",
+                                       "/",
+                                       "/"
+                               )
+                       },
+                       
+                       // No context or resource given.
+                       {
+                               input(
+                                       "NoContextOrResource-1",
+                                       "http://host:port","","","/path";,
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";,
+                                       "http://foo.com:123/foobar";
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextOrResource-2",
+                                       "http://host:port","","","/path";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextOrResource-3",
+                                       "http://host:port","","","/path";,
+                                       "/foobar",
+                                       "http://host:port/foobar";,
+                                       "/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextOrResource-4",
+                                       "http://host:port","","","/path";,
+                                       "/",
+                                       "http://host:port";,
+                                       "/"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextOrResource-5",
+                                       "http://host:port","","","/path";,
+                                       "foobar",
+                                       "http://host:port/foobar";,
+                                       "/foobar"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextOrResource-6",
+                                       "http://host:port","","","/path";,
+                                       "",
+                                       "http://host:port/path";,
+                                       "/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextOrResource-7",
+                                       "http://host:port","","","/path";,
+                                       "context:/foo",
+                                       "http://host:port/foo";,
+                                       "/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextOrResource-8",
+                                       "http://host:port","","","/path";,
+                                       "context:/",
+                                       "http://host:port";,
+                                       "/"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextOrResource-9",
+                                       "http://host:port","","","/path";,
+                                       "servlet:/foo",
+                                       "http://host:port/foo";,
+                                       "/foo"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextOrResource-10",
+                                       "http://host:port","","","/path";,
+                                       "servlet:/",
+                                       "http://host:port";,
+                                       "/"
+                               )
+                       },
+               });             
+       }
+       
+       public static Input input(String label, String authority, String 
context, String resource, String path, String uri, String expectedAbsolute, 
String expectedRootRelative) {
+               return new Input(label, authority, context, resource, path, 
uri, expectedAbsolute, expectedRootRelative);
+       }
+       
+       public static class Input {
+               private final UriContext uriContext;
+               private final String label, uri, expectedAbsolute, 
expectedRootRelative;
+               
+               public Input(String label, String authority, String context, 
String resource, String path, String uri, String expectedAbsolute, String 
expectedRootRelative) {
+                       this.label = label;
+                       this.uriContext = new UriContext(authority, context, 
resource, path);
+                       this.uri = uri;
+                       this.expectedAbsolute = expectedAbsolute;
+                       this.expectedRootRelative = expectedRootRelative;
+               }
+       }
+       
+       private Input in;
+       
+       public UriContextResolutionComboTest(Input in) throws Exception {
+               this.in = in;
+       }
+       
+       @Test
+       public void testAbsolute() {
+               assertEquals(in.expectedAbsolute, 
in.uriContext.resolveAbsolute(in.uri), "{0}: testAbsolute() failed", in.label);
+       }
+               
+       @Test
+       public void testRootRelative() {
+               assertEquals(in.expectedRootRelative, 
in.uriContext.resolveRootRelative(in.uri), "{0}: testRootRelative() failed", 
in.label);
+       }
+
+       @Test
+       public void testAbsoluteAppend() {
+               assertEquals(in.expectedAbsolute, 
in.uriContext.appendAbsolute(new StringBuilder(), in.uri).toString(), "{0}: 
testAbsolute() failed", in.label);
+       }
+               
+       @Test
+       public void testRootRelativeAppend() {
+               assertEquals(in.expectedRootRelative, 
in.uriContext.appendRootRelative(new StringBuilder(), in.uri).toString(), "{0}: 
testRootRelative() failed", in.label);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextUriComboTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextUriComboTest.java
 
b/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextUriComboTest.java
new file mode 100644
index 0000000..b3a3b60
--- /dev/null
+++ 
b/juneau-core-test/src/test/java/org/apache/juneau/utils/UriContextUriComboTest.java
@@ -0,0 +1,234 @@
+// 
***************************************************************************************************************************
+// * 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.utils;
+
+import static org.apache.juneau.TestUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.*;
+import org.junit.runner.*;
+import org.junit.runners.*;
+
+/**
+ * Verifies that the getUri() methods in UriContext work correctly.
+ */
+@RunWith(Parameterized.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class UriContextUriComboTest {
+
+       @Parameterized.Parameters
+       public static Collection<Object[]> getInput() {
+               return Arrays.asList(new Object[][] {
+
+                       // Happy cases - All URL parts known.
+                       {
+                               input(
+                                       "Happy-1",
+                                       
"http://foo.com:123","/context","/resource","/path";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123/context";,
+                                       "http://foo.com:123/context/resource";,
+                                       
"http://foo.com:123/context/resource/path";,
+                                       "/context",
+                                       "/context/resource",
+                                       "/context/resource/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "Happy-2",
+                                       
"http://foo.com:123","/c1/c2","/r1/r2","/p1/p2";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123/c1/c2";,
+                                       "http://foo.com:123/c1/c2/r1/r2";,
+                                       "http://foo.com:123/c1/c2/r1/r2/p1/p2";,
+                                       "/c1/c2",
+                                       "/c1/c2/r1/r2",
+                                       "/c1/c2/r1/r2/p1/p2"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthority-1",
+                                       "","/context","/resource","/path",
+                                       "/",
+                                       "/context",
+                                       "/context/resource",
+                                       "/context/resource/path",
+                                       "/context",
+                                       "/context/resource",
+                                       "/context/resource/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContext-1",
+                                       
"http://foo.com:123","","/resource","/path";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123/resource";,
+                                       "http://foo.com:123/resource/path";,
+                                       "/",
+                                       "/resource",
+                                       "/resource/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoResource-1",
+                                       
"http://foo.com:123","/context","","/path";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123/context";,
+                                       "http://foo.com:123/context";,
+                                       "http://foo.com:123/context/path";,
+                                       "/context",
+                                       "/context",
+                                       "/context/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoPath-1",
+                                       
"http://foo.com:123","/context","/resource","";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123/context";,
+                                       "http://foo.com:123/context/resource";,
+                                       "http://foo.com:123/context/resource";,
+                                       "/context",
+                                       "/context/resource",
+                                       "/context/resource"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityNoContext-1",
+                                       "","","/resource","/path",
+                                       "/",
+                                       "/",
+                                       "/resource",
+                                       "/resource/path",
+                                       "/",
+                                       "/resource",
+                                       "/resource/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoContextNoResource-1",
+                                       "http://foo.com:123","","","/path";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123";,
+                                       "http://foo.com:123/path";,
+                                       "/",
+                                       "/",
+                                       "/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "NoAuthorityNoContextNoResource-1",
+                                       "","","","/path",
+                                       "/",
+                                       "/",
+                                       "/",
+                                       "/path",
+                                       "/",
+                                       "/",
+                                       "/path"
+                               )
+                       },
+                       {
+                               input(
+                                       "Nothing-1",
+                                       "","","","",
+                                       "/",
+                                       "/",
+                                       "/",
+                                       "/",
+                                       "/",
+                                       "/",
+                                       "/"
+                               )
+                       },
+               });             
+       }
+       
+       public static Input input(String label, String authority, String 
context, String resource, String path, 
+                       String eAbsoluteAuthority, String eAbsoluteContext, 
String eAbsoluteResource, String eAbsolutePath, 
+                       String eRootRelativeContext, String 
eRootRelativeResource, String eRootRelativePath) {
+               return new Input(label, authority, context, resource, path, 
eAbsoluteAuthority, eAbsoluteContext, eAbsoluteResource, eAbsolutePath, 
eRootRelativeContext, eRootRelativeResource, eRootRelativePath);
+       }
+       
+       public static class Input {
+               private final UriContext uriContext;
+               private final String label, eAbsoluteAuthority, 
eAbsoluteContext, eAbsoluteResource, eAbsolutePath, eRootRelativeContext, 
eRootRelativeResource, eRootRelativePath;
+               
+               public Input(String label, String authority, String context, 
String resource, String path, 
+                                       String eAbsoluteAuthority, String 
eAbsoluteContext, String eAbsoluteResource, String eAbsolutePath, 
+                                       String eRootRelativeContext, String 
eRootRelativeResource, String eRootRelativePath) {
+                       this.label = label;
+                       this.uriContext = new UriContext(authority, context, 
resource, path);
+                       this.eAbsoluteAuthority = eAbsoluteAuthority;
+                       this.eAbsoluteContext = eAbsoluteContext;
+                       this.eAbsoluteResource = eAbsoluteResource;
+                       this.eAbsolutePath = eAbsolutePath;
+                       this.eRootRelativeContext = eRootRelativeContext;
+                       this.eRootRelativeResource = eRootRelativeResource;
+                       this.eRootRelativePath = eRootRelativePath;
+               }
+       }
+       
+       private Input in;
+       
+       public UriContextUriComboTest(Input in) throws Exception {
+               this.in = in;
+       }
+       
+       @Test
+       public void a1_testAbsoluteAuthority() {
+               assertEquals(in.eAbsoluteAuthority, 
in.uriContext.getAbsoluteAuthority(), "{0}: testAbsoluteAuthority() failed", 
in.label);
+       }
+
+       @Test
+       public void a2_testAbsoluteContext() {
+               assertEquals(in.eAbsoluteContext, 
in.uriContext.getAbsoluteContextRoot(), "{0}: testAbsoluteContext() failed", 
in.label);
+       }
+       
+       @Test
+       public void a3_testAbsoluteResource() {
+               assertEquals(in.eAbsoluteResource, 
in.uriContext.getAbsoluteServletPath(), "{0}: testAbsoluteResource() failed", 
in.label);
+       }
+       
+       @Test
+       public void a4_testAbsolutePath() {
+               assertEquals(in.eAbsolutePath, 
in.uriContext.getAbsolutePathInfo(), "{0}: testAbsolutePath() failed", 
in.label);
+       }
+       
+       @Test
+       public void a5_testRootRelativeContext() {
+               assertEquals(in.eRootRelativeContext, 
in.uriContext.getRootRelativeContextRoot(), "{0}: testRootRelativeContext() 
failed", in.label);
+       }
+       
+       @Test
+       public void a6_testRootRelativeResource() {
+               assertEquals(in.eRootRelativeResource, 
in.uriContext.getRootRelativeServletPath(), "{0}: testRootRelativeResource() 
failed", in.label);
+       }
+       
+       @Test
+       public void a7_testRootRelativePath() {
+               assertEquals(in.eRootRelativePath, 
in.uriContext.getRootRelativePathInfo(), "{0}: testRootRelativePath() failed", 
in.label);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core-test/src/test/java/org/apache/juneau/xml/XmlContentTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/xml/XmlContentTest.java 
b/juneau-core-test/src/test/java/org/apache/juneau/xml/XmlContentTest.java
index 6f0738e..65e23e7 100755
--- a/juneau-core-test/src/test/java/org/apache/juneau/xml/XmlContentTest.java
+++ b/juneau-core-test/src/test/java/org/apache/juneau/xml/XmlContentTest.java
@@ -46,7 +46,7 @@ public class XmlContentTest {
                t.f2 = null;
 
                sw = new StringWriter();
-               session = s1.createSession(sw, new 
ObjectMap("{"+SERIALIZER_trimNullProperties+":false}"), null, null, null, null);
+               session = s1.createSession(sw, new 
ObjectMap("{"+SERIALIZER_trimNullProperties+":false}"), null, null, null, null, 
null);
                s1.serialize(session, t);
                r = sw.toString();
                assertEquals("<A f1='f1'>_x0000_</A>", r);
@@ -54,7 +54,7 @@ public class XmlContentTest {
                assertEqualObjects(t, t2);
 
                sw = new StringWriter();
-               session = s2.createSession(sw, new 
ObjectMap("{"+SERIALIZER_trimNullProperties+":false}"), null, null, null, null);
+               session = s2.createSession(sw, new 
ObjectMap("{"+SERIALIZER_trimNullProperties+":false}"), null, null, null, null, 
null);
                s2.serialize(session, t);
                r = sw.toString();
                assertEquals("<A f1='f1'>_x0000_</A>\n", r);
@@ -154,7 +154,7 @@ public class XmlContentTest {
                t.f2 = null;
 
                sw = new StringWriter();
-               session = s1.createSession(sw, new 
ObjectMap("{"+SERIALIZER_trimNullProperties+":false}"), null, null, null, null);
+               session = s1.createSession(sw, new 
ObjectMap("{"+SERIALIZER_trimNullProperties+":false}"), null, null, null, null, 
null);
                s1.serialize(session, t);
                r = sw.toString();
                assertEquals("<A f1='f1'>_x0000_</A>", r);
@@ -162,7 +162,7 @@ public class XmlContentTest {
                assertEqualObjects(t, t2);
 
                sw = new StringWriter();
-               session = s2.createSession(sw, new 
ObjectMap("{"+SERIALIZER_trimNullProperties+":false}"), null, null, null, null);
+               session = s2.createSession(sw, new 
ObjectMap("{"+SERIALIZER_trimNullProperties+":false}"), null, null, null, null, 
null);
                s2.serialize(session, t);
                r = sw.toString();
                assertEquals("<A f1='f1'>_x0000_</A>\n", r);

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/BeanMap.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanMap.java 
b/juneau-core/src/main/java/org/apache/juneau/BeanMap.java
index e6a1566..af5634e 100644
--- a/juneau-core/src/main/java/org/apache/juneau/BeanMap.java
+++ b/juneau-core/src/main/java/org/apache/juneau/BeanMap.java
@@ -440,6 +440,17 @@ public class BeanMap<T> extends AbstractMap<String,Object> 
implements Delegate<T
        }
 
        /**
+        * Given a string containing variables of the form 
<code>"{property}"</code>, replaces those variables with
+        * property values in this bean.
+        *
+        * @param s The string containing variables.
+        * @return A new string with variables replaced, or the same string if 
no variables were found.
+        */
+       public String resolveVars(String s) {
+               return StringUtils.replaceVars(s, this);
+       }
+
+       /**
         * Returns a simple collection of properties for this bean map.
         * @return A simple collection of properties for this bean map.
         */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/UriContext.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/UriContext.java 
b/juneau-core/src/main/java/org/apache/juneau/UriContext.java
new file mode 100644
index 0000000..99ddd7f
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/UriContext.java
@@ -0,0 +1,421 @@
+// 
***************************************************************************************************************************
+// * 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;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.io.*;
+
+/**
+ * Represents a URL broken into authority/context-root/servlet-path/path-info 
parts.
+ * <p>
+ * A typical request against a URL takes the following form:
+ * <p class='bcode'>
+ *     http://host:port/context-root/servlet-path/path-info
+ *     |   authority   |  context   |  resource  |  path  |
+ *  +--------------------------------------------------+
+ * </p>
+ * <p>
+ * This class allows you to convert URL strings to absolute (e.g. 
<js>"http://host:port/foo/bar";</js>) or root-relative
+ *     (e.g. <js>"/foo/bar"</js>) URLs.
+ * <p>
+ * Two special protocols are used to represent context-root-relative and 
servlet-relative URIs:
+ *     <js>"context:/"</js> and <js>"servlet:/"</js>.
+ *
+ * The following list shows the types of URLs that can be resolved with this 
class:
+ * <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>"foo"</js> - Path-info-relative URI.
+ *     <li><js>""</js> - Path-info URI.
+ * </ul>
+ *
+ * The following class shows how
+ */
+public class UriContext {
+
+       private final String authority, contextRoot, servletPath, pathInfo, 
parentPath;
+
+       // Lazy-initialized fields.
+       private String aContextRoot, rContextRoot, aServletPath, rResource, 
aPathInfo, rPath;
+
+
+       /**
+        * Constructor.
+        * <p>
+        * Leading and trailing slashes are trimmed of all parameters.
+        * <p>
+        * Any parameter can be <jk>null</jk>.  Blanks and nulls are equivalent.
+        *
+        * @param authority - The authority portion of URL (e.g. 
<js>"http://hostname:port";</js>)
+        * @param contextRoot - The context root of the application (e.g. 
<js>"/context-root"</js>, or <js>"context-root"</js>)
+        * @param servletPath - The servlet path (e.g. 
<js>"/servlet-path"</js>, or <js>"servlet-path"</js>)
+        * @param pathInfo - The path info (e.g. <js>"/path-info"</js>, or 
<js>"path-info"</js>)
+        */
+       public UriContext(String authority, String contextRoot, String 
servletPath, String pathInfo) {
+               this.authority = nullIfEmpty(trimSlashes(authority));
+               this.contextRoot = nullIfEmpty(trimSlashes(contextRoot));
+               this.servletPath = nullIfEmpty(trimSlashes(servletPath));
+               this.pathInfo = nullIfEmpty(trimSlashes(pathInfo));
+               this.parentPath = this.pathInfo == null || 
this.pathInfo.indexOf('/') == -1 ? null : this.pathInfo.substring(0, 
this.pathInfo.lastIndexOf('/'));
+       }
+
+       /**
+        * Returns the absolute URI of just the authority portion of this URI 
context.
+        * <p>
+        * Example:  <js>"http://hostname:port";</js>
+        * <p>
+        * If the authority is null/empty, returns <js>"/"</js>.
+        *
+        * @return The absolute URI of just the authority portion of this URI 
context.
+        *      Never <jk>null</jk>.
+        */
+       public String getAbsoluteAuthority() {
+               return authority == null ? "/" : authority;
+       }
+
+       /**
+        * Returns the absolute URI of the context-root portion of this URI 
context.
+        * <p>
+        * Example:  <js>"http://hostname:port/context-root";</js>
+        *
+        * @return The absolute URI of the context-root portion of this URI 
context.
+        *      Never <jk>null</jk>.
+        */
+       public String getAbsoluteContextRoot() {
+               if (aContextRoot == null) {
+                       if (authority == null)
+                               aContextRoot = getRootRelativeContextRoot();
+                       else
+                               aContextRoot = (contextRoot == null ? authority 
: (authority + '/' + contextRoot));
+               }
+               return aContextRoot;
+       }
+
+       /**
+        * Returns the root-relative URI of the context portion of this URI 
context.
+        * <p>
+        * Example:  <js>"/context-root"</js>
+        *
+        * @return The root-relative URI of the context portion of this URI 
context.
+        *      Never <jk>null</jk>.
+        */
+       public String getRootRelativeContextRoot() {
+               if (rContextRoot == null)
+                       rContextRoot = contextRoot == null ? "/" : ('/' + 
contextRoot);
+               return rContextRoot;
+       }
+
+       /**
+        * Returns the absolute URI of the resource portion of this URI context.
+        * <p>
+        * Example:  <js>"http://hostname:port/context-root/servlet-path";</js>
+        *
+        * @return The absolute URI of the resource portion of this URI context.
+        *      Never <jk>null</jk>.
+        */
+       public String getAbsoluteServletPath() {
+               if (aServletPath == null) {
+                       if (authority == null)
+                               aServletPath = getRootRelativeServletPath();
+                       else {
+                               if (contextRoot == null)
+                                       aServletPath = (servletPath == null ? 
authority : authority + '/' + servletPath);
+                               else
+                                       aServletPath = (servletPath == null ? 
(authority + '/' + contextRoot) : (authority + '/' + contextRoot + '/' + 
servletPath));
+                       }
+               }
+               return aServletPath;
+       }
+
+       /**
+        * Returns the root-relative URI of the resource portion of this URI 
context.
+        * <p>
+        * Example:  <js>"/context-root/servlet-path"</js>
+        *
+        * @return The root-relative URI of the resource portion of this URI 
context.
+        *      Never <jk>null</jk>.
+        */
+       public String getRootRelativeServletPath() {
+               if (rResource == null) {
+                       if (contextRoot == null)
+                               rResource = (servletPath == null ? "/" : ('/' + 
servletPath));
+                       else
+                               rResource = (servletPath == null ? ('/' + 
contextRoot) : ('/' + contextRoot + '/' + servletPath));
+               }
+               return rResource;
+       }
+
+       /**
+        * Returns the parent of the URL returned by {@link 
#getAbsoluteServletPath()}.
+        *
+        * @return The parent of the URL returned by {@link 
#getAbsoluteServletPath()}.
+        */
+       public String getAbsoluteServletPathParent() {
+               return getParent(getAbsoluteServletPath());
+       }
+
+       /**
+        * Returns the parent of the URL returned by {@link 
#getRootRelativeServletPath()}.
+        *
+        * @return The parent of the URL returned by {@link 
#getRootRelativeServletPath()}.
+        */
+       public String getRootRelativeServletPathParent() {
+               return getParent(getRootRelativeServletPath());
+       }
+
+       /**
+        * Returns the absolute URI of the path portion of this URI context.
+        * <p>
+        * Example:  
<js>"http://hostname:port/context-root/servlet-path/path-info";</js>
+        *
+        * @return The absolute URI of the path portion of this URI context.
+        *      Never <jk>null</jk>.
+        */
+       public String getAbsolutePathInfo() {
+               if (aPathInfo == null) {
+                       if (authority == null)
+                               aPathInfo = getRootRelativePathInfo();
+                       else {
+                               if (contextRoot == null) {
+                                       if (servletPath == null)
+                                               aPathInfo = (pathInfo == null ? 
authority : (authority + '/' + pathInfo));
+                                       else
+                                               aPathInfo = (pathInfo == null ? 
(authority + '/' + servletPath) : (authority + '/' + servletPath + '/' + 
pathInfo));
+                               } else {
+                                       if (servletPath == null)
+                                               aPathInfo = (pathInfo == null ? 
authority + '/' + contextRoot : (authority + '/' + contextRoot + '/' + 
pathInfo));
+                                       else
+                                               aPathInfo = (pathInfo == null ? 
(authority + '/' + contextRoot + '/' + servletPath) : (authority + '/' + 
contextRoot + '/' + servletPath + '/' + pathInfo));
+                               }
+                       }
+               }
+               return aPathInfo;
+       }
+
+       /**
+        * Returns the root-relative URI of the path portion of this URI 
context.
+        * <p>
+        * Example:  <js>"/context-root/servlet-path/path-info"</js>
+        *
+        * @return The root-relative URI of the path portion of this URI 
context.
+        *      Never <jk>null</jk>.
+        */
+       public String getRootRelativePathInfo() {
+               if (rPath == null) {
+                       if (contextRoot == null) {
+                               if (servletPath == null)
+                                       rPath = (pathInfo == null ? "/" : ('/' 
+ pathInfo));
+                               else
+                                       rPath = (pathInfo == null ? ('/' + 
servletPath) : ('/' + servletPath + '/' + pathInfo));
+                       } else {
+                               if (servletPath == null)
+                                       rPath = (pathInfo == null ? ('/' + 
contextRoot) : ('/' + contextRoot + '/' + pathInfo));
+                               else
+                                       rPath = (pathInfo == null ? ('/' + 
contextRoot + '/' + servletPath) : ('/' + contextRoot + '/' + servletPath + '/' 
+ pathInfo));
+                       }
+               }
+               return rPath;
+       }
+
+       /**
+        * Returns the parent of the URL returned by {@link 
#getAbsolutePathInfo()}.
+        *
+        * @return The parent of the URL returned by {@link 
#getAbsolutePathInfo()}.
+        */
+       public String getAbsolutePathInfoParent() {
+               return getParent(getAbsolutePathInfo());
+       }
+
+       /**
+        * Returns the parent of the URL returned by {@link 
#getRootRelativePathInfo()}.
+        *
+        * @return The parent of the URL returned by {@link 
#getRootRelativePathInfo()}.
+        */
+       public String getRootRelativePathInfoParent() {
+               return getParent(getRootRelativePathInfo());
+       }
+
+       /**
+        * Converts the specified URI to absolute form based on values in this 
context.
+        *
+        * @param uri The URI to convert to absolute form.
+        * @return The converted URI.
+        */
+       public String resolveAbsolute(String uri) {
+               if (isAbsoluteUri(uri))
+                       return uri;
+               return appendAbsolute(new StringBuilder(), uri).toString();
+       }
+
+       /**
+        * Converts the specified URI to root-relative form based on values in 
this context.
+        *
+        * @param uri The URI to convert to root-relative form.
+        * @return The converted URI.
+        */
+       public String resolveRootRelative(String uri) {
+               if (isAbsoluteUri(uri))
+                       return uri;
+               if (startsWith(uri, '/'))
+                       return uri;
+               return appendRootRelative(new StringBuilder(), uri).toString();
+       }
+
+       /**
+        * Same as {@link #resolveAbsolute(String)} except appends result to 
the specified appendable.
+        *
+        * @param a The appendable to append the URL to.
+        * @param uri The URI to convert to absolute form.
+        * @return The same appendable passed in.
+        */
+       public Appendable appendAbsolute(Appendable a, String uri) {
+
+               try {
+                       uri = nullIfEmpty(uri);
+
+                       // Absolute paths are not changed.
+                       if (isAbsoluteUri(uri))
+                               return a.append(uri);
+
+                       // Root-relative path
+                       if (startsWith(uri, '/')) {
+                               if (authority != null){
+                                       a.append(authority);
+                                       if (uri.length() == 1)
+                                               return a;
+                               }
+                               return a.append(uri);
+
+                       // Context-relative path
+                       } else if (uri != null && uri.startsWith("context:/")) {
+                               if (authority != null)
+                                       a.append(authority);
+                               if (contextRoot != null)
+                                       a.append('/').append(contextRoot);
+                               if (uri.length() > 9)
+                                       a.append('/').append(uri.substring(9));
+                               else if (contextRoot == null && authority == 
null)
+                                       a.append('/');
+
+                       // Resource-relative path
+                       } else if (uri != null && uri.startsWith("servlet:/")) {
+                               if (authority != null)
+                                       a.append(authority);
+                               if (contextRoot != null)
+                                       a.append('/').append(contextRoot);
+                               if (servletPath != null)
+                                       a.append('/').append(servletPath);
+                               if (uri.length() > 9)
+                                       a.append('/').append(uri.substring(9));
+                               else if (servletPath == null && contextRoot == 
null && authority == null)
+                                       a.append('/');
+
+                       // Relative path
+                       } else {
+                               if (authority != null)
+                                       a.append(authority);
+                               if (contextRoot != null)
+                                       a.append('/').append(contextRoot);
+                               if (servletPath != null)
+                                       a.append('/').append(servletPath);
+                               if (uri == null) {
+                                       if (pathInfo != null)
+                                               a.append('/').append(pathInfo);
+                               } else {
+                                       if (parentPath != null)
+                                               
a.append('/').append(parentPath);
+                                       a.append('/').append(uri);
+                               }
+                       }
+
+                       return a;
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       /**
+        * Same as {@link #resolveRootRelative(String)} except appends result 
to the specified appendable.
+        *
+        * @param a The appendable to append the URL to.
+        * @param uri The URI to convert to root-relative form.
+        * @return The same appendable passed in.
+        */
+       public Appendable appendRootRelative(Appendable a, String uri) {
+
+               try {
+                       uri = nullIfEmpty(uri);
+
+                       // Absolute paths are not changed.
+                       if (isAbsoluteUri(uri))
+                               return a.append(uri);
+
+                       // Root-relative path
+                       if (startsWith(uri, '/')) {
+                               return a.append(uri);
+
+                       // Context-relative path
+                       } else if (uri != null && uri.startsWith("context:/")) {
+                               if (contextRoot != null)
+                                       a.append('/').append(contextRoot);
+                               if (uri.length() > 9)
+                                       a.append('/').append(uri.substring(9));
+                               else if (contextRoot == null)
+                                       a.append('/');
+
+                       // Resource-relative path
+                       } else if (uri != null && uri.startsWith("servlet:/")) {
+                               if (contextRoot != null)
+                                       a.append('/').append(contextRoot);
+                               if (servletPath != null)
+                                       a.append('/').append(servletPath);
+                               if (uri.length() > 9)
+                                       a.append('/').append(uri.substring(9));
+                               else if (servletPath == null && contextRoot == 
null)
+                                       a.append('/');
+
+                       // Relative path
+                       } else {
+                               if (contextRoot != null)
+                                       a.append('/').append(contextRoot);
+                               if (servletPath != null)
+                                       a.append('/').append(servletPath);
+                               if (uri == null) {
+                                       if (pathInfo != null)
+                                               a.append('/').append(pathInfo);
+                               } else {
+                                       if (parentPath != null)
+                                               
a.append('/').append(parentPath);
+                                       a.append('/').append(uri);
+                               }
+                       }
+
+                       return a;
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       private static String getParent(String uri) {
+               int i = uri.lastIndexOf('/');
+               if (i <= 1)
+                       return "/";
+               return uri.substring(0, i);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/UriRelativity.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/UriRelativity.java 
b/juneau-core/src/main/java/org/apache/juneau/UriRelativity.java
new file mode 100644
index 0000000..95fc46b
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/UriRelativity.java
@@ -0,0 +1,29 @@
+// 
***************************************************************************************************************************
+// * 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;
+
+/**
+ * Identifies how relative URIs should resolve against.
+ */
+public enum UriRelativity {
+
+       /**
+        * Relative URIs should be considered relative to the servlet URI.
+        */
+       RESOURCE,
+
+       /**
+        * Relative URIs should be considered relative to the request URI.
+        */
+       PATH_INFO;
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/UriResolution.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/UriResolution.java 
b/juneau-core/src/main/java/org/apache/juneau/UriResolution.java
new file mode 100644
index 0000000..aacfe6d
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/UriResolution.java
@@ -0,0 +1,34 @@
+// 
***************************************************************************************************************************
+// * 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;
+
+/**
+ * Identifies the possible types of URL resolution.
+ */
+public enum UriResolution {
+
+       /**
+        * Resolve to an absolute URL (e.g. 
<js>"http://host:port/context-root/servlet-path/path-info";</js>).
+        */
+       ABSOLUTE,
+
+       /**
+        * Resolve to a root-relative URL (e.g. 
<js>"/context-root/servlet-path/path-info"</js>).
+        */
+       ROOT_RELATIVE,
+
+       /**
+        * Don't do any URL resolution.
+        */
+       NONE;
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java 
b/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
index 163928b..16a6c14 100644
--- a/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
@@ -30,18 +30,20 @@ public final class CsvSerializerSession extends 
SerializerSession {
         * Create a new session using properties specified in the context.
         *
         * @param ctx The context creating this session object.
-        * The context contains all the configuration settings for this object.
+        *      The context contains all the configuration settings for this 
object.
         * @param output The output object.
         * @param op The override properties.
-        * These override any context properties defined in the context.
+        *      These override any context properties defined in the context.
         * @param javaMethod The java method that called this serializer, 
usually the method in a REST servlet.
         * @param locale The session locale.
-        * If <jk>null</jk>, then the locale defined on the context is used.
+        *      If <jk>null</jk>, then the locale defined on the context is 
used.
         * @param timeZone The session timezone.
-        * If <jk>null</jk>, then the timezone defined on the context is used.
+        *      If <jk>null</jk>, then the timezone defined on the context is 
used.
         * @param mediaType The session media type (e.g. 
<js>"application/json"</js>).
+        * @param uriContext The URI context.
+        *      Identifies the current request URI used for resolution of URIs 
to absolute or root-relative form.
         */
-       protected CsvSerializerSession(CsvSerializerContext ctx, ObjectMap op, 
Object output, Method javaMethod, Locale locale, TimeZone timeZone, MediaType 
mediaType) {
-               super(ctx, op, output, javaMethod, locale, timeZone, mediaType);
+       protected CsvSerializerSession(CsvSerializerContext ctx, ObjectMap op, 
Object output, Method javaMethod, Locale locale, TimeZone timeZone, MediaType 
mediaType, UriContext uriContext) {
+               super(ctx, op, output, javaMethod, locale, timeZone, mediaType, 
uriContext);
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/HtmlBeanPropertyMeta.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlBeanPropertyMeta.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlBeanPropertyMeta.java
index a2c3a6c..63ecbb4 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlBeanPropertyMeta.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlBeanPropertyMeta.java
@@ -18,36 +18,58 @@ import org.apache.juneau.html.annotation.*;
 /**
  * Metadata on bean properties specific to the HTML serializers and parsers 
pulled from the {@link Html @Html} annotation on the bean property.
  */
-public class HtmlBeanPropertyMeta extends BeanPropertyMetaExtended {
+@SuppressWarnings("rawtypes")
+public final class HtmlBeanPropertyMeta extends BeanPropertyMetaExtended {
 
-       private boolean asXml, noTables, noTableHeaders, asPlainText;
+       private final boolean asXml, noTables, noTableHeaders, asPlainText;
+       private final HtmlRender render;
+       private final String link;
 
        /**
         * Constructor.
         *
         * @param bpm The metadata of the bean property of this additional 
metadata.
+        * @throws Exception If render class could not be instantiated.
         */
-       public HtmlBeanPropertyMeta(BeanPropertyMeta bpm) {
+       public HtmlBeanPropertyMeta(BeanPropertyMeta bpm) throws Exception {
                super(bpm);
+               Builder b = new Builder();
                if (bpm.getField() != null)
-                       findHtmlInfo(bpm.getField().getAnnotation(Html.class));
+                       
b.findHtmlInfo(bpm.getField().getAnnotation(Html.class));
                if (bpm.getGetter() != null)
-                       findHtmlInfo(bpm.getGetter().getAnnotation(Html.class));
+                       
b.findHtmlInfo(bpm.getGetter().getAnnotation(Html.class));
                if (bpm.getSetter() != null)
-                       findHtmlInfo(bpm.getSetter().getAnnotation(Html.class));
+                       
b.findHtmlInfo(bpm.getSetter().getAnnotation(Html.class));
+
+               this.asXml = b.asXml;
+               this.noTables = b.noTables;
+               this.noTableHeaders = b.noTableHeaders;
+               this.asPlainText = b.asPlainText;
+               this.render = b.render.newInstance();
+               this.link = b.link;
        }
 
-       private void findHtmlInfo(Html html) {
-               if (html == null)
-                       return;
-               if (html.asXml())
-                       asXml = html.asXml();
-               if (html.noTables())
-                       noTables = html.noTables();
-               if (html.noTableHeaders())
-                       noTableHeaders = html.noTableHeaders();
-               if (html.asPlainText())
-                       asPlainText = html.asPlainText();
+       private static class Builder {
+               boolean asXml, noTables, noTableHeaders, asPlainText;
+               Class<? extends HtmlRender> render = HtmlRender.class;
+               String link;
+
+               void findHtmlInfo(Html html) {
+                       if (html == null)
+                               return;
+                       if (html.asXml())
+                               asXml = html.asXml();
+                       if (html.noTables())
+                               noTables = html.noTables();
+                       if (html.noTableHeaders())
+                               noTableHeaders = html.noTableHeaders();
+                       if (html.asPlainText())
+                               asPlainText = html.asPlainText();
+                       if (html.render() != HtmlRender.class)
+                               render = html.render();
+                       if (! html.link().isEmpty())
+                               link = html.link();
+               }
        }
 
        /**
@@ -85,4 +107,26 @@ public class HtmlBeanPropertyMeta extends 
BeanPropertyMetaExtended {
        public boolean isNoTableHeaders() {
                return noTableHeaders;
        }
+
+       /**
+        * Returns the render class for rendering the style and contents of 
this property value in HTML.
+        * <p>
+        * This value is specified via the {@link Html#render()} annotation.
+        *
+        * @return The render class, never <jk>null</jk>.
+        */
+       public HtmlRender getRender() {
+               return render;
+       }
+
+       /**
+        * Adds a hyperlink to this value in HTML.
+        * <p>
+        * This value is specified via the {@link Html#link()} annotation.
+        *
+        * @return The link string, or <jk>null</jk> if not specified.
+        */
+       public String getLink() {
+               return link;
+       }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
index d97afe4..68e19e2 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
@@ -75,8 +75,8 @@ public class HtmlDocSerializer extends 
HtmlStrippedDocSerializer {
        
//--------------------------------------------------------------------------------
 
        @Override /* Serializer */
-       public HtmlDocSerializerSession createSession(Object output, ObjectMap 
op, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) {
-               return new HtmlDocSerializerSession(ctx, op, output, 
javaMethod, locale, timeZone, mediaType);
+       public HtmlDocSerializerSession createSession(Object output, ObjectMap 
op, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType, 
UriContext uriContext) {
+               return new HtmlDocSerializerSession(ctx, op, output, 
javaMethod, locale, timeZone, mediaType, uriContext);
        }
 
        @Override /* Serializer */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
index 0e334d1..f04d2b7 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
@@ -41,20 +41,22 @@ public final class HtmlDocSerializerSession extends 
HtmlSerializerSession {
         * Create a new session using properties specified in the context.
         *
         * @param ctx The context creating this session object.
-        * The context contains all the configuration settings for this object.
+        *      The context contains all the configuration settings for this 
object.
         * @param output The output object.  See {@link 
JsonSerializerSession#getWriter()} for valid class types.
         * @param op The override properties.
-        * These override any context properties defined in the context.
+        *      These override any context properties defined in the context.
         * @param javaMethod The java method that called this serializer, 
usually the method in a REST servlet.
         * @param locale The session locale.
-        * If <jk>null</jk>, then the locale defined on the context is used.
+        *      If <jk>null</jk>, then the locale defined on the context is 
used.
         * @param timeZone The session timezone.
-        * If <jk>null</jk>, then the timezone defined on the context is used.
+        *      If <jk>null</jk>, then the timezone defined on the context is 
used.
         * @param mediaType The session media type (e.g. 
<js>"application/json"</js>).
+        * @param uriContext The URI context.
+        *      Identifies the current request URI used for resolution of URIs 
to absolute or root-relative form.
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
-       protected HtmlDocSerializerSession(HtmlDocSerializerContext ctx, 
ObjectMap op, Object output, Method javaMethod, Locale locale, TimeZone 
timeZone, MediaType mediaType) {
-               super(ctx, op, output, javaMethod, locale, timeZone, mediaType);
+       protected HtmlDocSerializerSession(HtmlDocSerializerContext ctx, 
ObjectMap op, Object output, Method javaMethod, Locale locale, TimeZone 
timeZone, MediaType mediaType, UriContext uriContext) {
+               super(ctx, op, output, javaMethod, locale, timeZone, mediaType, 
uriContext);
                if (op == null || op.isEmpty()) {
                        title = ctx.title;
                        text = ctx.text;
@@ -131,6 +133,6 @@ public final class HtmlDocSerializerSession extends 
HtmlSerializerSession {
                Object output = getOutput();
                if (output instanceof HtmlWriter)
                        return (HtmlWriter)output;
-               return new HtmlWriter(super.getWriter(), isUseWhitespace(), 
isTrimStrings(), getQuoteChar(), getRelativeUriBase(), 
getAbsolutePathUriBase());
+               return new HtmlWriter(super.getWriter(), isUseWhitespace(), 
isTrimStrings(), getQuoteChar(), getRelativeUriBase(), 
getAbsolutePathUriBase(), getUriContext());
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/HtmlRender.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlRender.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlRender.java
new file mode 100644
index 0000000..b30c67e
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlRender.java
@@ -0,0 +1,149 @@
+// 
***************************************************************************************************************************
+// * 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.html;
+
+import org.apache.juneau.html.annotation.*;
+import org.apache.juneau.serializer.*;
+
+/**
+ * Allows custom rendering of bean property values when serialized as HTML.
+ * <p>
+ * Associated with bean properties using the {@link Html#render() 
@Html.render()} annotation.
+ * <p>
+ * Using this class, you can alter the CSS style and HTML content of the bean 
property.
+ * <p>
+ * The following example shows two render classes that customize the 
appearance of the <code>pctFull</code> and
+ *     <code>status</code> columns shown below:
+ * <p>
+ * <img class='bordered' src='doc-files/HtmlRender_1.png'>
+ *
+ * <p class='bcode'>
+ *
+ *     <jc>// Our bean class</jc>
+ *     <jk>public class</jk> FileSpace {
+ *
+ *             <jk>private final</jk> String <jf>drive</jf>;
+ *             <jk>private final long</jk> <jf>total</jf>, <jf>available</jf>;
+ *
+ *             <jk>public</jk> FileSpace(String drive, <jk>long</jk> total, 
<jk>long</jk> available) {
+ *                     <jk>this</jk>.<jf>drive</jf> = drive;
+ *                     <jk>this</jk>.<jf>total</jf> = total;
+ *                     <jk>this</jk>.<jf>available</jf> = available;
+ *             }
+ *
+ *             <ja>@Html</ja>(link=<js>"drive/{drive}"</js>)
+ *             <jk>public</jk> String getDrive() {
+ *                     <jk>return</jk> <jf>drive</jf>;
+ *             }
+ *
+ *             <jk>public long</jk> getTotal() {
+ *                     <jk>return</jk> <jf>total</jf>;
+ *             }
+ *
+ *             <jk>public long</jk> getAvailable() {
+ *                     <jk>return</jk> <jf>available</jf>;
+ *             }
+ *
+ *             <ja>@Html</ja>(render=FileSpacePctRender.<jk>class</jk>)
+ *             <jk>public float</jk> getPctFull() {
+ *                     <jk>return</jk> ((100 * <jf>available</jf>) / 
<jf>total</jf>);
+ *             }
+ *
+ *             <ja>@Html</ja>(render=FileSpaceStatusRender.<jk>class</jk>)
+ *             <jk>public</jk> FileSpaceStatus getStatus() {
+ *                     <jk>float</jk> pf = getPctFull();
+ *                     <jk>if</jk> (pf < 80)
+ *                             <jk>return</jk> FileSpaceStatus.<jsf>OK</jsf>;
+ *                     <jk>if</jk> (pf < 90)
+ *                             <jk>return</jk> 
FileSpaceStatus.<jsf>WARNING</jsf>;
+ *                     <jk>return</jk> FileSpaceStatus.<jsf>SEVERE</jsf>;
+ *             }
+ *     }
+ *
+ *     <jc>// Possible values for the getStatus() method</jc>
+ *     <jk>public static enum</jk> FileSpaceStatus {
+ *             <jsf>OK</jsf>, <jsf>WARNING</jsf>, <jsf>SEVERE</jsf>;
+ *     }
+ *
+ *     <jc>// Custom render for getPctFull() method</jc>
+ *     <jk>public static class</jk> FileSpacePctRender <jk>extends</jk> 
HtmlRender&lt;Float&gt; {
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public</jk> String getStyle(SerializerSession session, 
Float value) {
+ *                     <jk>if</jk> (value < 80)
+ *                             <jk>return</jk> 
<js>"background-color:lightgreen;text-align:center"</js>;
+ *                     <jk>if</jk> (value < 90)
+ *                             <jk>return</jk> 
<js>"background-color:yellow;text-align:center"</js>;
+ *                     <jk>return</jk> 
<js>"background-color:red;text-align:center;border:;animation:color_change 0.5s 
infinite alternate"</js>;
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public</jk> Object getContent(SerializerSession session, 
Float value) {
+ *                     <jk>if</jk> (value >= 90)
+ *                             <jk>return</jk> <jsm>div</jsm>(
+ *                                     
String.<jsm>format</jsm>(<js>"%.0f%%"</js>, value),
+ *                                     <jsm>style</jsm>(<js>"@keyframes 
color_change { from { background-color: red; } to { background-color: yellow; 
}"</js>)
+ *                             );
+ *                     <jk>return</jk> 
String.<jsm>format</jsm>(<js>"%.0f%%"</js>, value);
+ *             }
+ *     }
+ *
+ *     <jc>// Custom render for getStatus() method</jc>
+ *     <jk>public static class</jk> FileSpaceStatusRender <jk>extends</jk> 
HtmlRender&lt;FileSpaceStatus&gt; {
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public</jk> String getStyle(SerializerSession session, 
FileSpaceStatus value) {
+ *                     <jk>return</jk> <js>"text-align:center"</js>;
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public</jk> Object getContent(SerializerSession session, 
FileSpaceStatus value) {
+ *                     <jk>switch</jk> (value) {
+ *                             <jk>case</jk> <jsf>OK</jsf>:  <jk>return</jk> 
<jsm>img</jsm>().src(URI.<jsm>create</jsm>(<js>"servlet:/htdocs/ok.png"</js>));
+ *                             <jk>case</jk> <jsf>WARNING</jsf>:  
<jk>return</jk> 
<jsm>img</jsm>().src(URI.<jsm>create</jsm>(<js>"servlet:/htdocs/warning.png"</js>));
+ *                             <jk>default</jk>: <jk>return</jk> 
<jsm>img</jsm>().src(URI.<jsm>create</jsm>(<js>"servlet:/htdocs/severe.png"</js>));
+ *                     }
+ *             }
+ *     }
+ * </p>
+ * @param <T> The bean property type.
+ */
+public class HtmlRender<T> {
+
+       /**
+        * Returns the CSS style of the element containing the bean property 
value.
+        *
+        * @param session The current serializer session.
+        *      Can be used to retrieve properties and session-level 
information.
+        * @param value The bean property value.
+        * @return The CSS style string, or <jk>null</jk> if no style should be 
added.
+        */
+       public String getStyle(SerializerSession session, T value) {
+               return null;
+       }
+
+       /**
+        * Returns the delegate value for the specified bean property value.
+        * <p>
+        * The default implementation simply returns the same value.
+        * A typical use is to return an HTML element using one of the HTML5 
DOM beans.
+        *
+        * @param session The current serializer session.
+        *      Can be used to retrieve properties and session-level 
information.
+        * @param value The bean property value.
+        * @return The new bean property value.
+        */
+       public Object getContent(SerializerSession session, T value) {
+               return value;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java
index bc17387..fabcdaa 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java
@@ -74,8 +74,8 @@ public final class HtmlSchemaDocSerializer extends 
HtmlDocSerializer {
        }
 
        @Override /* Serializer */
-       public HtmlDocSerializerSession createSession(Object output, ObjectMap 
op, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) {
-               return new HtmlDocSerializerSession(ctx, op, output, 
javaMethod, locale, timeZone, mediaType);
+       public HtmlDocSerializerSession createSession(Object output, ObjectMap 
op, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType, 
UriContext uriContext) {
+               return new HtmlDocSerializerSession(ctx, op, output, 
javaMethod, locale, timeZone, mediaType, uriContext);
        }
 
        @Override /* ISchemaSerializer */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java
index c3d1088..91b8f16 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java
@@ -400,6 +400,7 @@ public class HtmlSerializer extends XmlSerializer {
                out.eTag(i, "table").nl();
        }
 
+       @SuppressWarnings({ "rawtypes", "unchecked" })
        private void serializeBeanMap(HtmlSerializerSession session, HtmlWriter 
out, BeanMap<?> m, ClassMeta<?> eType, BeanPropertyMeta ppMeta) throws 
Exception {
                int i = session.getIndent();
 
@@ -420,6 +421,9 @@ public class HtmlSerializer extends XmlSerializer {
                for (BeanPropertyValue p : m.getValues(session.isTrimNulls())) {
                        BeanPropertyMeta pMeta = p.getMeta();
                        ClassMeta<?> cMeta = p.getClassMeta();
+                       HtmlBeanPropertyMeta hbpMeta = 
pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class);
+                       String link = hbpMeta.getLink();
+                       HtmlRender render = hbpMeta.getRender();
 
                        String key = p.getName();
                        Object value = p.getValue();
@@ -432,11 +436,20 @@ public class HtmlSerializer extends XmlSerializer {
 
                        out.sTag(i+1, "tr").nl();
                        out.sTag(i+2, "td").text(key).eTag("td").nl();
-                       out.sTag(i+2, "td");
+                       out.oTag(i+2, "td");
+                       String style = render.getStyle(session, value);
+                       if (style != null)
+                               out.attr("style", style);
+                       out.cTag();
+
                        try {
-                               ContentResult cr = serializeAnything(session, 
out, value, cMeta, key, 2, pMeta, false);
+                               if (link != null) 
+                                       out.oTag(i+3, "a").attrUri("href", 
m.resolveVars(link)).cTag();
+                               ContentResult cr = serializeAnything(session, 
out, render.getContent(session, value), cMeta, key, 2, pMeta, false);
                                if (cr == CR_NORMAL)
                                        out.i(i+2);
+                               if (link != null) 
+                                       out.eTag("a");
                        } catch (SerializeException e) {
                                throw e;
                        } catch (Error e) {
@@ -533,10 +546,23 @@ public class HtmlSerializer extends XmlSerializer {
                                        for (Object k : th) {
                                                BeanMapEntry p = 
m2.getProperty(session.toString(k));
                                                BeanPropertyMeta pMeta = 
p.getMeta();
-                                               out.sTag(i+2, "td");
-                                               ContentResult cr = 
serializeAnything(session, out, p.getValue(), pMeta.getClassMeta(), 
p.getKey().toString(), 2, pMeta, false);
+                                               HtmlBeanPropertyMeta hpMeta = 
pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class);
+                                               String link = hpMeta.getLink();
+                                               HtmlRender render = 
hpMeta.getRender();
+
+                                               Object value = p.getValue();
+                                               out.oTag(i+2, "td");
+                                               String style = 
render.getStyle(session, value);
+                                               if (style != null)
+                                                       out.attr("style", 
style);
+                                               out.cTag();
+                                               if (link != null) 
+                                                       out.oTag(i+3, 
"a").attrUri("href", m2.resolveVars(link)).cTag();
+                                               ContentResult cr = 
serializeAnything(session, out, render.getContent(session, value), 
pMeta.getClassMeta(), p.getKey().toString(), 2, pMeta, false);
                                                if (cr == CR_NORMAL)
                                                        out.i(i+2);
+                                               if (link != null) 
+                                                       out.eTag("a");
                                                out.eTag("td").nl();
                                        }
                                }
@@ -689,8 +715,8 @@ public class HtmlSerializer extends XmlSerializer {
        
//--------------------------------------------------------------------------------
 
        @Override /* Serializer */
-       public HtmlSerializerSession createSession(Object output, ObjectMap op, 
Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) {
-               return new HtmlSerializerSession(ctx, op, output, javaMethod, 
locale, timeZone, mediaType);
+       public HtmlSerializerSession createSession(Object output, ObjectMap op, 
Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType, 
UriContext uriContext) {
+               return new HtmlSerializerSession(ctx, op, output, javaMethod, 
locale, timeZone, mediaType, uriContext);
        }
 
        @Override /* Serializer */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
index 3faf271..48b16dd 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
@@ -52,19 +52,21 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
         * Create a new session using properties specified in the context.
         *
         * @param ctx The context creating this session object.
-        * The context contains all the configuration settings for this object.
+        *      The context contains all the configuration settings for this 
object.
         * @param output The output object.  See {@link 
JsonSerializerSession#getWriter()} for valid class types.
         * @param op The override properties.
-        * These override any context properties defined in the context.
+        *      These override any context properties defined in the context.
         * @param javaMethod The java method that called this serializer, 
usually the method in a REST servlet.
         * @param locale The session locale.
-        * If <jk>null</jk>, then the locale defined on the context is used.
+        *      If <jk>null</jk>, then the locale defined on the context is 
used.
         * @param timeZone The session timezone.
-        * If <jk>null</jk>, then the timezone defined on the context is used.
+        *      If <jk>null</jk>, then the timezone defined on the context is 
used.
         * @param mediaType The session media type (e.g. 
<js>"application/json"</js>).
+        * @param uriContext The URI context.
+        *      Identifies the current request URI used for resolution of URIs 
to absolute or root-relative form.
         */
-       protected HtmlSerializerSession(HtmlSerializerContext ctx, ObjectMap 
op, Object output, Method javaMethod, Locale locale, TimeZone timeZone, 
MediaType mediaType) {
-               super(ctx, op, output, javaMethod, locale, timeZone, mediaType);
+       protected HtmlSerializerSession(HtmlSerializerContext ctx, ObjectMap 
op, Object output, Method javaMethod, Locale locale, TimeZone timeZone, 
MediaType mediaType, UriContext uriContext) {
+               super(ctx, op, output, javaMethod, locale, timeZone, mediaType, 
uriContext);
                String labelParameter;
                if (op == null || op.isEmpty()) {
                        anchorText = Enum.valueOf(AnchorText.class, 
ctx.uriAnchorText);
@@ -91,7 +93,7 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
                Object output = getOutput();
                if (output instanceof HtmlWriter)
                        return (HtmlWriter)output;
-               return new HtmlWriter(super.getWriter(), isUseWhitespace(), 
isTrimStrings(), getQuoteChar(), getRelativeUriBase(), 
getAbsolutePathUriBase());
+               return new HtmlWriter(super.getWriter(), isUseWhitespace(), 
isTrimStrings(), getQuoteChar(), getRelativeUriBase(), 
getAbsolutePathUriBase(), getUriContext());
        }
 
        /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/HtmlWriter.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlWriter.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlWriter.java
index 95b6092..30114c3 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlWriter.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlWriter.java
@@ -14,6 +14,7 @@ package org.apache.juneau.html;
 
 import java.io.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.xml.*;
 
@@ -31,9 +32,11 @@ public class HtmlWriter extends XmlWriter {
         * @param quoteChar The quote character to use (i.e. <js>'\''</js> or 
<js>'"'</js>)
         * @param uriContext The web application context path (e.g. 
"/contextRoot").
         * @param uriAuthority The web application URI authority (e.g. 
"http://hostname:9080";)
+        * @param uriContext2 The URI context.
+        *      Identifies the current request URI used for resolution of URIs 
to absolute or root-relative form.
         */
-       public HtmlWriter(Writer out, boolean useWhitespace, boolean 
trimStrings, char quoteChar, String uriContext, String uriAuthority) {
-               super(out, useWhitespace, trimStrings, quoteChar, uriContext, 
uriAuthority, false, null);
+       public HtmlWriter(Writer out, boolean useWhitespace, boolean 
trimStrings, char quoteChar, String uriContext, String uriAuthority, UriContext 
uriContext2) {
+               super(out, useWhitespace, trimStrings, quoteChar, uriContext, 
uriAuthority, uriContext2, false, null);
        }
 
        /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/SimpleHtmlWriter.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/SimpleHtmlWriter.java 
b/juneau-core/src/main/java/org/apache/juneau/html/SimpleHtmlWriter.java
index 2ae07f8..b0b6cc3 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/SimpleHtmlWriter.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/SimpleHtmlWriter.java
@@ -28,7 +28,7 @@ public class SimpleHtmlWriter extends HtmlWriter {
         * Constructor.
         */
        public SimpleHtmlWriter() {
-               super(new StringWriter(), true, false, '\'', null, null);
+               super(new StringWriter(), true, false, '\'', null, null, null);
        }
 
        @Override /* Object */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/annotation/Html.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/annotation/Html.java 
b/juneau-core/src/main/java/org/apache/juneau/html/annotation/Html.java
index c63cd2e..dcfc53c 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/annotation/Html.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/annotation/Html.java
@@ -53,4 +53,39 @@ public @interface Html {
         * Default is <jk>false</jk>.
         */
        boolean noTableHeaders() default false;
+
+       /**
+        * Associates an {@link HtmlRender} with a bean property for custom 
HTML rendering of the property.
+        * <p>
+        * This annotation applies to bean properties only.
+        */
+       @SuppressWarnings("rawtypes")
+       Class<? extends HtmlRender> render() default HtmlRender.class;
+
+       /**
+        * Adds a hyperlink to a bean property when rendered as HTML.
+        * <p>
+        * The text can contain any bean property values resolved through 
variables of the form <js>"{property-name}"</js>.
+        * <p>
+        * The URLs can be any of the following forms:
+        * <ul>
+        *      <li>Absolute - e.g. 
<js>"http://host:123/myContext/myServlet/myPath";</js>
+        *      <li>Context-root-relative - e.g. 
<js>"/myContext/myServlet/myPath"</js>
+        *      <li>Context-relative - e.g. <js>"context:/myServlet/myPath"</js>
+        *      <li>Servlet-relative - e.g. <js>"servlet:/myPath"</js>
+        *      <li>Path-info-relative - e.g. <js>"myPath"</js>
+        * </ul>
+        *
+        * <h6 class='figure'>Example:</h6>
+        * <p class='bcode'>
+        *      <jk>public class</jk> FileSpace {
+        *
+        *              <ja>@Html</ja>(link=<js>"servlet:/drive/{drive}"</js>)
+        *              <jk>public</jk> String getDrive() {
+        *                      ...;
+        *              }
+        *      }
+        * </p>
+        */
+       String link() default "";
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e1a50566/juneau-core/src/main/java/org/apache/juneau/html/doc-files/HtmlRender_1.png
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/doc-files/HtmlRender_1.png 
b/juneau-core/src/main/java/org/apache/juneau/html/doc-files/HtmlRender_1.png
new file mode 100644
index 0000000..f070aea
Binary files /dev/null and 
b/juneau-core/src/main/java/org/apache/juneau/html/doc-files/HtmlRender_1.png 
differ

Reply via email to