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

[1/3] incubator-juneau git commit: Add QueryWidget support.

Repository: incubator-juneau
Updated Branches:
  refs/heads/master f83685984 -> 095900928


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java
index a563863..a6ce725 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java
@@ -12,14 +12,8 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest.converters;
 
-import static javax.servlet.http.HttpServletResponse.*;
-
-import java.util.*;
-
 import org.apache.juneau.*;
-import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.*;
-import org.apache.juneau.serializer.*;
 import org.apache.juneau.utils.*;
 
 /**
@@ -30,65 +24,39 @@ import org.apache.juneau.utils.*;
  * <p>
  * The following HTTP request parameters are available for tabular data (e.g. {@code Collections} of {@code Maps}, arrays of beans, etc...):
  * <ul class='spaced-list'>
- * 	<li><b>&amp;q=<i>JSON-object</i></b> - Query parameter.  Only return rows that match the specified search string. <br>
- * 			The JSON object keys are column names, and the values are search parameter strings.<br>
- * 			Example:  <code>&amp;s=(name=Bill*,birthDate=&gt;2000)</code>
- * 	<li><b>&amp;v=<i>JSON-array or comma-delimited list</i></b> - View parameter.  Only return the specified columns.<br>
- * 			Example:  <code>&amp;v=(name,birthDate)</code>
- * 	<li><b>&amp;s=<i>JSON-object</i></b> - Sort parameter.  Sort the results by the specified columns.<br>
- * 			The JSON object keys are the column names, and the values are either {@code 'A'} for ascending or {@code 'D'} for descending.
- * 			Example:  <code>&amp;s=(name=A,birthDate=D)</code>
- * 	<li><b>&amp;i=<i>true/false</i></b> - Case-insensitive parameter.  Specify <jk>true</jk> for case-insensitive matching on the {@code &amp;q} parameter.
- * 	<li><b>&amp;p=<i>number</i></b> - Position parameter.  Only return rows starting at the specified index position (zero-indexed).  Default is {@code 0}.
- * 	<li><b>&amp;q=<i>number</i></b> - Limit parameter.  Only return the specified number of rows. Default is {@code 0} (meaning return all rows).
+ * 	<li><code>&amp;s=</code> Search arguments.
+ * 			<br>Comma-delimited list of key/value pairs representing column names and search tokens.
+ * 			<br>Example:  <js>"&amp;s=name=Bill*,birthDate&gt;2000"</js>
+ * 	<li><code>&amp;v=</code> Visible columns.
+ * 			<br>Comma-delimited list of column names to display.
+ * 			<br>Example:  <js>"&amp;v=name,birthDate"</js>
+ * 	<li><code>&amp;o=</code> Sort commands.
+ * 			<br>Comma-delimited list of columns to sort by.
+ * 			<br>Column names can be suffixed with <js>'+'</js> or <js>'-'</js> to indicate ascending or descending order.
+ * 			<br>The default is ascending order.
+ * 			<br>Example:  <js>"&amp;o=name,birthDate-"</js>
+ * 	<li><code>&amp;i=</code> Case-insensitive parameter.
+ * 			<br>Boolean flag for case-insensitive matching on the search parameters.
+ * 	<li><code>&amp;p=</code> - Position parameter.
+ * 			<br>Only return rows starting at the specified index position (zero-indexed).
+ * 			<br>Default is {@code 0}.
+ * 	<li><code>&amp;l=</code> Limit parameter.
+ * 			<br>Only return the specified number of rows.
+ * 			<br>Default is {@code 0} (meaning return all rows).
  * </ul>
  *
  * <p>
- * The <b>&amp;v</b> parameter can also be used on {@code Maps} and beans.
- *
- * <p>
  * See {@link PojoQuery} for additional information on filtering POJO models.
  */
 public final class Queryable implements RestConverter {
 
 	@Override /* RestConverter */
-	@SuppressWarnings({ "unchecked", "rawtypes" })
-	public Object convert(RestRequest req, Object o, ClassMeta cm) {
+	public Object convert(RestRequest req, Object o, ClassMeta<?> cm) {
 		if (o == null)
 			return null;
-
-		try {
-			RequestQuery q = req.getQuery();
-
-			// If no actual filtering parameters have been passed in, and there is no map augmenter specified,
-			// then just pass the original object back.
-			if (q.containsAnyKeys("q","v","s","g","i","p","l")) {
-				BeanSession session = req.getBeanSession();
-
-				if (cm.getPojoSwap() != null)
-					o = cm.getPojoSwap().swap(session, o);
-
-				PojoQuery f = new PojoQuery(o, session);
-
-				if (o instanceof Collection || o.getClass().isArray()) {
-					ObjectMap query = q.get("q", ObjectMap.class);
-					List<String> view = q.get("v", List.class, String.class);
-					List sort = q.get("s", List.class, String.class);
-					boolean ignoreCase = q.get("i", false, Boolean.class);
-					int pos = q.get("p", 0, Integer.class);
-					int limit = q.get("l", 0, Integer.class);
-					o = f.filterCollection(query, view, sort, pos, limit, ignoreCase);
-
-				} else {
-					List<String> view = q.get("v", List.class, String.class);
-					o = f.filterMap(view);
-				}
-			}
+		SearchArgs searchArgs = req.getQuery().getSearchArgs();
+		if (searchArgs == null)
 			return o;
-		} catch (SerializeException e) {
-			throw new RestException(SC_BAD_REQUEST, e);
-		} catch (ParseException e) {
-			throw new RestException(SC_BAD_REQUEST, e);
-		}
+		return new PojoQuery(o, req.getBeanSession()).filter(searchArgs);
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/package.html
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/package.html b/juneau-rest/src/main/java/org/apache/juneau/rest/package.html
index d5dec8b..7c04791 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/package.html
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/package.html
@@ -2091,14 +2091,14 @@
 					<ul>
 						<li><ck>$R{attribute.X}</ck> - Value returned by {@link org.apache.juneau.rest.RestRequest#getAttribute(String)} converted to a string.
 						<li><ck>$R{contextPath}</ck> - Value returned by {@link org.apache.juneau.rest.RestRequest#getContextPath()}.
-						<li><ck>$R{formData.X}</ck> - Value returned by {@link org.apache.juneau.rest.RequestFormData#getFirst(String)}.
-						<li><ck>$R{header.X}</ck> - Value returned by {@link org.apache.juneau.rest.RequestHeaders#getFirst(String)}.
+						<li><ck>$R{formData.X}</ck> - Value returned by {@link org.apache.juneau.rest.RequestFormData#getString(String)}.
+						<li><ck>$R{header.X}</ck> - Value returned by {@link org.apache.juneau.rest.RequestHeaders#getString(String)}.
 						<li><ck>$R{method}</ck> - Value returned by {@link org.apache.juneau.rest.RestRequest#getMethod()}.
 						<li><ck>$R{methodSummary}</ck> - Value returned by {@link org.apache.juneau.rest.RestRequest#getMethodSummary()}.
 						<li><ck>$R{methodDescription}</ck> - Value returned by {@link org.apache.juneau.rest.RestRequest#getMethodDescription()}.
 						<li><ck>$R{path.X}</ck> - Value returned by {@link org.apache.juneau.rest.RequestPathMatch#get(Object)}.
 						<li><ck>$R{pathInfo}</ck> - Value returned by {@link org.apache.juneau.rest.RestRequest#getPathInfo()}.
-						<li><ck>$R{query.X}</ck> - Value returned by {@link org.apache.juneau.rest.RequestQuery#getFirst(String)}.
+						<li><ck>$R{query.X}</ck> - Value returned by {@link org.apache.juneau.rest.RequestQuery#getString(String)}.
 						<li><ck>$R{requestParentURI}</ck> - Value returned by {@link org.apache.juneau.UriContext#getRootRelativePathInfoParent()}.
 						<li><ck>$R{requestURI}</ck> - Value returned by {@link org.apache.juneau.rest.RestRequest#getRequestURI()}.
 						<li><ck>$R{servletDescription}</ck> - Value returned by {@link org.apache.juneau.rest.RestRequest#getServletDescription()}.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java b/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java
index fe7aab6..fad726e 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java
@@ -38,7 +38,7 @@ public class DefaultHandler implements ResponseHandler {
 	@Override /* ResponseHandler */
 	public boolean handle(RestRequest req, RestResponse res, Object output) throws IOException, RestException {
 		SerializerGroup g = res.getSerializerGroup();
-		String accept = req.getHeaders().getFirst("Accept", "");
+		String accept = req.getHeaders().getString("Accept", "");
 		SerializerMatch sm = g.getSerializerMatch(accept);
 		if (sm != null) {
 			Serializer s = sm.getSerializer();
@@ -87,7 +87,7 @@ public class DefaultHandler implements ResponseHandler {
 		} else {
 			throw new RestException(SC_NOT_ACCEPTABLE,
 				"Unsupported media-type in request header ''Accept'': ''{0}''\n\tSupported media-types: {1}",
-				req.getHeaders().getFirst("Accept", ""), g.getSupportedMediaTypes()
+				req.getHeaders().getString("Accept", ""), g.getSupportedMediaTypes()
 			);
 		}
 		return true;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java b/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java
index aeaefbd..3764cf8 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java
@@ -72,9 +72,9 @@ public class RequestVar extends SimpleVar {
 					if ("path".equals(prefix))
 						return req.getPathMatch().get(remainder);
 					if ("query".equals(prefix))
-						return req.getQuery().getFirst(remainder);
+						return req.getQuery().getString(remainder);
 					if ("formData".equals(prefix))
-						return req.getFormData().getFirst(remainder);
+						return req.getFormData().getString(remainder);
 					if ("header".equals(prefix))
 						return req.getHeader(remainder);
 					if ("attribute".equals(prefix))

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/QueryWidget.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/QueryWidget.java b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/QueryWidget.java
new file mode 100644
index 0000000..c21aa21
--- /dev/null
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/QueryWidget.java
@@ -0,0 +1,130 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest.widget;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.converters.*;
+
+/**
+ * Adds a <code>QUERY</code> link to the page that allows you to perform search/view/sort/paging on the page data.
+ * <p>
+ * A typical usage of the query widget is to include it as a navigation link as shown in the example below
+ * 	pulled from the <code>PetStoreResource</code> example:
+ * <p class='bcode'>
+ * 	<ja>@RestResource</ja>(
+ * 		widgets={
+ * 			QueryWidget.<jk>class</jk>
+ * 		},
+ * 		htmldoc=<ja>@HtmlDoc</ja>(
+ * 			links=<js>"{up:'...',options:'...',query:'$W{query}',source:'...'}"</js>
+ * 		)
+ * 	)
+ * </p>
+ * <p>
+ * In the above example, this adds a <code>QUERY</code> that displays a search popup that can be used for
+ * 	filtering the page results...
+ * <p>
+ * <img class='bordered' src='doc-files/PetStore_Query.png'>
+ * <p>
+ * Tooltips are provided by hovering over the field names.
+ * <p>
+ * <img class='bordered' src='doc-files/PetStore_Query_tooltip.png'>
+ * <p>
+ * When submitted, the form submits a GET request against the current URI with special GET search API query parameters.
+ * (e.g. <js>"?s=column1=Foo*&amp;v=column1,column2&amp;o=column1,column2-&amp;p=100&amp;l=100"</js>).
+ * <p>
+ * The search arguments can be retrieved programmatically using {@link RequestQuery#getSearchArgs()}.
+ * <p>
+ * Typically, the search functionality is implemented by applying the predefined {@link Queryable} converter on the
+ * 	method that's returning a 2-dimensional table of POJOs that you wish to filter:
+ * <p class='bcode'>
+ * 	<ja>@RestMethod</ja>(
+ * 		name=<js>"GET"</js>,
+ * 		path=<js>"/"</js>,
+ * 		converters=Queryable.<jk>class</jk>
+ * 	)
+ * 	<jk>public</jk> Collection&lt;Pet&gt; getPets() {
+ * </p>
+ * <p>
+ * The following shows various search arguments and their results on the page:
+ * <table style='width:auto'>
+ * 	<tr>
+ * 		<th>Search type</th><th>Query arguments</th><th>Query results</th>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>No arguments</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q1.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r1.png'></td>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>String search</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q2.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r2.png'></td>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>Numeric range</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q3.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r3.png'></td>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>ANDed terms</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q4.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r4.png'></td>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>Date range (entire year)</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q8.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r8.png'></td>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>Date range</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q9.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r9.png'></td>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>Date range</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q10.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r10.png'></td>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>Hide columns</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q5.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r5.png'></td>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>Sort</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q6.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r6.png'></td>
+ * 	</tr>
+ * 	<tr>
+ * 		<td>Sort descending</td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_q7.png'></td>
+ * 		<td><img class='bordered' src='doc-files/PetStore_Query_r7.png'></td>
+ * 	</tr>
+ * </table>
+ *
+ */
+public class QueryWidget extends Widget {
+
+	@Override
+	public String getName() {
+		return "query";
+	}
+
+	@Override /* Widget */
+	public String resolve(RestRequest req) throws Exception {
+		// Note we're stripping off the license header.
+		return IOUtils.read(getClass().getResourceAsStream("QueryWidget.html")).replaceFirst("(?s)<!--(.*?)-->\\s*", "");
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query.png
new file mode 100644
index 0000000..0db9c0d
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q1.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q1.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q1.png
new file mode 100644
index 0000000..65b08dc
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q1.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q10.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q10.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q10.png
new file mode 100644
index 0000000..3ec59c1
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q10.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q2.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q2.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q2.png
new file mode 100644
index 0000000..935b2f0
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q2.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q3.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q3.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q3.png
new file mode 100644
index 0000000..a0b72c7
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q3.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q4.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q4.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q4.png
new file mode 100644
index 0000000..b324817
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q4.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q5.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q5.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q5.png
new file mode 100644
index 0000000..3fc6aab
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q5.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q6.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q6.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q6.png
new file mode 100644
index 0000000..d3fbd7c
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q6.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q7.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q7.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q7.png
new file mode 100644
index 0000000..ba8c997
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q7.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q8.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q8.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q8.png
new file mode 100644
index 0000000..e42fbbe
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q8.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q9.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q9.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q9.png
new file mode 100644
index 0000000..7cd2ffe
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_q9.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r1.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r1.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r1.png
new file mode 100644
index 0000000..dcc26b3
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r1.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r10.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r10.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r10.png
new file mode 100644
index 0000000..8cf450b
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r10.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r2.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r2.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r2.png
new file mode 100644
index 0000000..5904efa
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r2.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r3.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r3.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r3.png
new file mode 100644
index 0000000..4adf7cc
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r3.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r4.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r4.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r4.png
new file mode 100644
index 0000000..5904efa
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r4.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r5.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r5.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r5.png
new file mode 100644
index 0000000..9b6185a
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r5.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r6.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r6.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r6.png
new file mode 100644
index 0000000..d0f21be
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r6.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r8.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r8.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r8.png
new file mode 100644
index 0000000..c3450d8
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r8.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r9.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r9.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r9.png
new file mode 100644
index 0000000..8cf450b
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_r9.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_tooltip.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_tooltip.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_tooltip.png
new file mode 100644
index 0000000..4373661
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/PetStore_Query_tooltip.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/Petstore_Query_r7.png
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/Petstore_Query_r7.png b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/Petstore_Query_r7.png
new file mode 100644
index 0000000..472a2ce
Binary files /dev/null and b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/doc-files/Petstore_Query_r7.png differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryWidget.html
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryWidget.html b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryWidget.html
new file mode 100644
index 0000000..66ae23a
--- /dev/null
+++ b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryWidget.html
@@ -0,0 +1,139 @@
+<!--
+/***************************************************************************************************************************
+ * 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.
+ *
+ ***************************************************************************************************************************/
+ -->
+<a class='link' href='javascript:document.getElementById("query").classList.remove("hidden")'>query</a>
+<style>
+	.queryInput, .queryInput table {
+		padding:3px;
+		border: 1px solid black;
+		border-radius:3px;
+		background-color:#fefefe;
+		box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5);
+		position: fixed;
+		font-size: 9pt;
+    	color: #26343F;
+    	white-space: nowrap;
+    	text-align: left;
+    	margin:20px;
+	}
+
+	.hidden {
+		display:none;
+	}
+	
+	.tooltip {
+	    position: relative;
+	    display: inline-block;	    
+	}
+
+	.tooltip .tooltiptext {
+	    visibility: hidden;
+	    background-color: #FEF9E7;
+	    color: black;
+	    padding: 5px;
+	    border-radius: 6px;
+	    position: absolute;
+	    z-index: 1;
+	    top: 0;
+	    left: 0;
+	    margin-left: 30px;
+		box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5);
+	    opacity: 0;
+	    transition: opacity 0.5s;
+	    font-weight: normal;
+	}
+
+	.tooltip:hover .tooltiptext {
+	    visibility: visible;
+	    opacity: 1;
+	}	
+	
+	.tooltiptext {
+		white-space: nowrap;
+		float: left;
+	}
+}
+
+</style>
+<form id='queryForm' style='display:inline'>
+	<table id='query' class='queryInput hidden'>
+		<tr>
+			<th>
+				<div class="tooltip">
+					Search <small>(?)</small>:
+					<span class="tooltiptext">
+						Comma-delimited list of key/value pair search terms.
+						<br>Keys are column names.
+						<br>Values are search terms.
+						<br>Refer to the <code>Queryable</code> javadocs for a full explanation of possible search terms.
+						<br>Example: <code>column1=foo*, column2&lt;100, column3=2013-2016.06.30</code>
+					</span>
+				</div>
+			</th>
+			<td>
+				<input name="s" size="50">
+			</td>
+		</tr>
+		<tr>
+			<th>
+				<div class="tooltip">
+					View <small>(?)</small>:
+					<span class="tooltiptext">
+						Comma-delimited list of columns to display.
+						<br>Example: <code>column1, column2</code>
+					</span>
+				</div>
+			</th>
+			<td>
+				<input name="v" size="50">
+			</td>
+		</tr>
+		<tr>
+			<th>
+				<div class="tooltip">
+					Sort <small>(?)</small>:
+					<span class="tooltiptext">
+						Comma-delimited list of columns to sort by.
+						<br>Columns can be suffixed with '-' to indicate descending order.
+						<br>Example: <code>column1, column2-</code>
+					</span>
+				</div>
+			</th>
+			<td>
+				<input name="o" size="50">
+			</td>
+		</tr>
+		<tr>
+			<th>
+				Page:
+			</th>
+			<td>
+				Position: <input name='p' type='number' style='width:50px' step=20 min=0 value='0'>
+				Limit: <input name='l' type='number' style='width:50px' step=20 min=0 value='0'>
+				Ignore-case: <input name='i' type='checkbox' value='true'>
+			</td>
+		</tr>
+		<tr>
+			<th>
+				&nbsp;
+			</th>
+			<td style='float:right'>
+				<input type='button' value='Cancel' onclick='getElementById("query").classList.add("hidden")'>
+				<input type="submit" value='Submit'>
+			</td>
+		</tr>
+	</table>
+</form>
+


[2/3] incubator-juneau git commit: Add QueryWidget support.

Posted by ja...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/utils/SearchArgs.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/utils/SearchArgs.java b/juneau-core/src/main/java/org/apache/juneau/utils/SearchArgs.java
new file mode 100644
index 0000000..80cbbad
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/utils/SearchArgs.java
@@ -0,0 +1,301 @@
+// ***************************************************************************************************************************
+// * 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 java.util.Collections.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.internal.*;
+
+/**
+ * Encapsulates arguments for basic search/view/sort/position/limit functionality.
+ */
+public class SearchArgs {
+	private final Map<String,String> search;
+	private final List<String> view;
+	private final Map<String,Boolean> sort;
+	private final int position, limit;
+	private final boolean ignoreCase;
+
+	private SearchArgs(Builder b) {
+		this.search = unmodifiableMap(new LinkedHashMap<String,String>(b.search));
+		this.view = unmodifiableList(new ArrayList<String>(b.view));
+		this.sort = unmodifiableMap(new LinkedHashMap<String,Boolean>(b.sort));
+		this.position = b.position;
+		this.limit = b.limit;
+		this.ignoreCase = b.ignoreCase;
+	}
+
+	/**
+	 * Builder for {@link SearchArgs} class.
+	 */
+	@SuppressWarnings("hiding")
+	public static class Builder {
+		Map<String,String> search = new LinkedHashMap<String,String>();
+		List<String> view = new ArrayList<String>();
+		Map<String,Boolean> sort = new LinkedHashMap<String,Boolean>();
+		int position, limit;
+		boolean ignoreCase;
+
+		/**
+		 * Adds search terms to this builder.
+		 * <p>
+		 * The search terms are a comma-delimited list of key/value pairs of column-names and search tokens.
+		 * <p>
+		 * For example:
+		 * <p class='bcode'>
+		 * 	builder.search(<js>"column1=foo*, column2=bar baz"</js>);
+		 * </p>
+		 * <p>
+		 * It's up to implementers to decide the syntax and meaning of the search terms.
+		 * <p>
+		 * Whitespace is trimmed from column names and search tokens.
+		 *
+		 * @param searchTerms The search terms string.
+		 * 	Can be <jk>null</jk>.
+		 * @return This object (for method chaining).
+		 */
+		public Builder search(String searchTerms) {
+			if (searchTerms != null) {
+				for (String s : StringUtils.split(searchTerms, ',')) {
+					int i = StringUtils.indexOf(s, '=', '>', '<');
+					if (i == -1)
+						throw new RuntimeException("Invalid search terms: '"+searchTerms+"'");
+					char c = s.charAt(i);
+					search(s.substring(0, i).trim(), s.substring(c == '=' ? i+1 : i).trim());
+				}
+			}
+			return this;
+		}
+
+		/**
+		 * Adds a search term to this builder.
+		 * <p>
+		 * It's up to implementers to decide the syntax and meaning of the search term.
+		 *
+		 * @param column The column being searched.
+		 * @param searchTerm The search term.
+		 * @return This object (for method chaining).
+		 */
+		public Builder search(String column, String searchTerm) {
+			this.search.put(column, searchTerm);
+			return this;
+		}
+
+		/**
+		 * Specifies the list of columns to view.
+		 * <p>
+		 * The columns argument is a simple comma-delimited list of column names.
+		 * <p>
+		 * For example:
+		 * <p class='bcode'>
+		 * 	builder.view(<js>"column1, column2"</js>);
+		 * </p>
+		 * <p>
+		 * Whitespace is trimmed from column names.
+		 * <p>
+		 * Empty view columns imply view all columns.
+		 *
+		 * @param columns The columns being viewed.
+		 * 	Can be <jk>null</jk>.
+		 * @return This object (for method chaining).
+		 */
+		public Builder view(String columns) {
+			if (columns != null)
+				return view(Arrays.asList(StringUtils.split(columns, ',')));
+			return this;
+		}
+
+		/**
+		 * Specifies the list of columns to view.
+		 * <p>
+		 * Empty view columns imply view all columns.
+		 *
+		 * @param columns The columns being viewed.
+		 * @return This object (for method chaining).
+		 */
+		public Builder view(Collection<String> columns) {
+			this.view.addAll(columns);
+			return this;
+		}
+
+		/**
+		 * Specifies the sort arguments.
+		 * <p>
+		 * The sort argument is a simple comma-delimited list of column names.
+		 * <br>Column names can be suffixed with <js>'+'</js> or <js>'-'</js> to indicate ascending or descending order.
+		 * <br>No suffix implies ascending order.
+		 * <p>
+		 * For example:
+		 * <p class='bcode'>
+		 * 	<jc>// Order by column1 ascending, then column2 descending.</jc>
+		 * 	builder.sort(<js>"column1, column2-"</js>);
+		 * </p>
+		 * <p>
+		 * Note that the order of the order arguments is important.
+		 * <p>
+		 * Whitespace is trimmed from column names.
+		 *
+		 * @param sortArgs The columns to sort by.
+		 * 	Can be <jk>null</jk>.
+		 * @return This object (for method chaining).
+		 */
+		public Builder sort(String sortArgs) {
+			if (sortArgs != null)
+				sort(Arrays.asList(StringUtils.split(sortArgs, ',')));
+			return this;
+		}
+
+		/**
+		 * Specifies the sort arguments.
+		 * <p>
+		 * <br>Column names can be suffixed with <js>'+'</js> or <js>'-'</js> to indicate ascending or descending order.
+		 * <br>No suffix implies ascending order.
+		 * <p>
+		 * Note that the order of the sort is important.
+		 *
+		 * @param sortArgs The columns to sort by.
+		 * 	Can be <jk>null</jk>.
+		 * @return This object (for method chaining).
+		 */
+		public Builder sort(Collection<String> sortArgs) {
+			for (String s : sortArgs) {
+				boolean isDesc = false;
+				if (endsWith(s, '-', '+')) {
+					isDesc = endsWith(s, '-');
+					s = s.substring(0, s.length()-1);
+				}
+				this.sort.put(s, isDesc);
+			}
+			return this;
+		}
+
+		/**
+		 * Specifies the starting line number.
+		 *
+		 * @param position The zero-indexed position.
+		 * @return This object (for method chaining).
+		 */
+		public Builder position(int position) {
+			this.position = position;
+			return this;
+		}
+
+		/**
+		 * Specifies the number of rows to return.
+		 *
+		 * @param limit The number of rows to return.
+		 * 	If <code>&lt;=0</code>, all rows should be returned.
+		 * @return This object (for method chaining).
+		 */
+		public Builder limit(int limit) {
+			this.limit = limit;
+			return this;
+		}
+
+		/**
+		 * Specifies whether case-insensitive search should be used.
+		 * <p>
+		 * The default is <jk>false</jk>.
+		 *
+		 * @param value The ignore-case flag value.
+		 * @return This object (for method chaining).
+		 */
+		public Builder ignoreCase(boolean value) {
+			this.ignoreCase = value;
+			return this;
+		}
+
+		/**
+		 * Construct the {@link SearchArgs} object.
+		 * <p>
+		 * This method can be called multiple times to construct new objects.
+		 *
+		 * @return A new {@link SearchArgs} object initialized with values in this builder.
+		 */
+		public SearchArgs build() {
+			return new SearchArgs(this);
+		}
+	}
+
+	/**
+	 * The query search terms.
+	 * <p>
+	 * The search terms are key/value pairs consisting of column-names and search tokens.
+	 * <p>
+	 * It's up to implementers to decide the syntax and meaning of the search term.
+	 *
+	 * @return An unmodifiable map of query search terms.
+	 */
+	public Map<String,String> getSearch() {
+		return search;
+	}
+
+	/**
+	 * The view columns.
+	 * <p>
+	 * The view columns are the list of columns that should be displayed.
+	 * An empty list implies all columns should be displayed.
+	 *
+	 * @return An unmodifiable list of columns to view.
+	 */
+	public List<String> getView() {
+		return view;
+	}
+
+	/**
+	 * The sort columns.
+	 * <p>
+	 * The sort columns are key/value pairs consisting of column-names and direction flags
+	 * 	(<jk>false</jk> = ascending, <jk>true</jk> = descending).
+	 *
+	 * @return An unmodifiable ordered map of sort columns and directions.
+	 */
+	public Map<String,Boolean> getSort() {
+		return sort;
+	}
+
+	/**
+	 * The first-row position.
+	 *
+	 * @return The zero-indexed row number of the first row to display.
+	 * 	Default is <code>0</code>
+	 */
+	public int getPosition() {
+		return position;
+	}
+
+	/**
+	 * The number of rows to return.
+	 *
+	 * @return The number of rows to return in the result.
+	 * 	Default is <code>0</code> which means return all rows.
+	 */
+	public int getLimit() {
+		return limit;
+	}
+
+	/**
+	 * The ignore-case flag.
+	 * <p>
+	 * Used in conjunction with {@link #getSearch()} to specify whether case-insensitive searches should be performed.
+	 *
+	 * @return The number of rows to return in the result.
+	 * 	Default is <jk>false</jk>.
+	 */
+	public boolean isIgnoreCase() {
+		return ignoreCase;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/javadoc/overview.html
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/javadoc/overview.html b/juneau-core/src/main/javadoc/overview.html
index a2c57dd..194b659 100644
--- a/juneau-core/src/main/javadoc/overview.html
+++ b/juneau-core/src/main/javadoc/overview.html
@@ -6511,6 +6511,7 @@
 					<li>{@link org.apache.juneau.rest.widget.PoweredByJuneauWidget}
 					<li>{@link org.apache.juneau.rest.widget.ContentTypeLinksColumnWidget}
 					<li>{@link org.apache.juneau.rest.widget.ContentTypeLinksRowWidget}
+					<li>{@link org.apache.juneau.rest.widget.QueryWidget}
 				</ul>
 			<li><code>devops.css</code> cleaned up.
 			<li>Removed a bunch of URL-related methods from {@link org.apache.juneau.rest.RestRequest}.  

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java
index 381c897..a24e6ee 100644
--- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java
+++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java
@@ -23,7 +23,10 @@ import org.apache.juneau.json.*;
 import org.apache.juneau.microservice.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.converters.*;
+import org.apache.juneau.rest.widget.*;
 import org.apache.juneau.serializer.*;
+import org.apache.juneau.transforms.*;
 
 /**
  * Sample REST resource that renders summary and detail views of the same bean.
@@ -32,13 +35,17 @@ import org.apache.juneau.serializer.*;
 	title="Pet Store",
 	description="An example of a typical REST resource where beans are rendered in summary and details views.",
 	path="/petstore",
+	widgets={
+		QueryWidget.class
+	},
 	htmldoc=@HtmlDoc(
-		links="{up:'request:/..',options:'servlet:/?method=OPTIONS',source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/PetStoreResource.java'}",
+		links="{up:'request:/..',options:'servlet:/?method=OPTIONS',query:'$W{query}',source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/PetStoreResource.java'}",
 		aside=""
 			+ "<div style='max-width:400px' class='text'>"
 			+ "	<p>This page shows a standard REST resource that renders bean summaries and details.</p>"
 			+ "	<p>It shows how different properties can be rendered on the same bean in different views.</p>"
 			+ "	<p>It also shows examples of HtmlRender classes and @BeanProperty(format) annotations.</p>"
+			+ "	<p>It also shows how the Queryable converter and query widget can be used to create searchable interfaces.</p>"
 			+ "</div>"
 	)
 )
@@ -56,7 +63,13 @@ public class PetStoreResource extends Resource {
 	}
 	
 	// Exclude the 'breed' and 'getsAlongWith' properties from the beans.
-	@RestMethod(name="GET", path="/", summary="The complete list of pets in the store", bpExcludes="{Pet:'breed,getsAlongWith'}")
+	@RestMethod(
+		name="GET", 
+		path="/", 
+		summary="The complete list of pets in the store", 
+		bpExcludes="{Pet:'breed,getsAlongWith'}",
+		converters=Queryable.class
+	)
 	public Collection<Pet> getPets() {
 		return petDB.values();
 	}
@@ -81,6 +94,15 @@ public class PetStoreResource extends Resource {
 		
 		@BeanProperty(format="$%.2f")  // Renders price in dollars.
 		public float price;
+		
+		@BeanProperty(swap=DateSwap.RFC2822D.class)  // Renders dates in RFC2822 format.
+		public Date birthDate;
+	
+		public int getAge() {
+			Calendar c = new GregorianCalendar();
+			c.setTime(birthDate);
+			return new GregorianCalendar().get(Calendar.YEAR) - c.get(Calendar.YEAR); 
+		}
 	}
 	
 	@Html(render=KindRender.class)  // Render as an icon in HTML.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json b/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json
index e8d3800..09abfbd 100644
--- a/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json
+++ b/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json
@@ -11,7 +11,7 @@
 // * specific language governing permissions and limitations under the License.                                              *
 // ***************************************************************************************************************************
 {
-	1: {kind:'CAT',name:'Mr. Frisky',price:39.99,breed:'Persian',getsAlongWith:['CAT','FISH','RABBIT']},
-	2: {kind:'DOG',name:'Kibbles',price:99.99,breed:'Husky',getsAlongWith:['DOG','BIRD','FISH','MOUSE','RABBIT']},
-	3: {kind:'RABBIT',name:'Hoppy',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE']}
+	1: {kind:'CAT',name:'Mr. Frisky',price:39.99,breed:'Persian',getsAlongWith:['CAT','FISH','RABBIT'],birthDate:'04 Jul 2012'},
+	2: {kind:'DOG',name:'Kibbles',price:99.99,breed:'Husky',getsAlongWith:['DOG','BIRD','FISH','MOUSE','RABBIT'],birthDate:'01 Sep 2014'},
+	3: {kind:'RABBIT',name:'Hoppy',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE'],birthDate:'16 Apr 2017'}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java b/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java
index b37a4ed..a9093cf 100644
--- a/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java
+++ b/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java
@@ -190,40 +190,40 @@ public class AddressBookResourceTest extends RestTestcase {
 			int rc = client.doGet("/addressBook?method=init").run();
 			assertEquals(200, rc);
 
-			r = client.doGet("/addressBook/people?q=(name=B*)");
+			r = client.doGet("/addressBook/people?s=name=B*");
 			people = r.getResponse(PersonList.class);
 			assertEquals(1, people.size());
 			assertEquals("Barack Obama", people.get(0).name);
 
-			r = client.doGet("/addressBook/people?q=(name='~'Barack+Obama~'')");
+			r = client.doGet("/addressBook/people?s=name='Barack+Obama'");
 			people = r.getResponse(PersonList.class);
 			assertEquals(1, people.size());
 			assertEquals("Barack Obama", people.get(0).name);
 
-			r = client.doGet("/addressBook/people?q=(name='~'Barack%20Obama~'')");
+			r = client.doGet("/addressBook/people?s=name='Barack%20Obama'");
 			people = r.getResponse(PersonList.class);
 			assertEquals(1, people.size());
 			assertEquals("Barack Obama", people.get(0).name);
 
-			r = client.doGet("/addressBook/people?v=@(name,birthDate)");
+			r = client.doGet("/addressBook/people?v=name,birthDate");
 			people = r.getResponse(PersonList.class);
 			assertEquals("Barack Obama", people.get(0).name);
 			assertTrue(people.get(0).getAge() > 10);
 			assertEquals(0, people.get(0).addresses.size());
 
-			r = client.doGet("/addressBook/people?v=@(addresses,birthDate)");
+			r = client.doGet("/addressBook/people?v=addresses,birthDate");
 			people = r.getResponse(PersonList.class);
 			assertNull(people.get(0).name);
 			assertTrue(people.get(0).getAge() > 10);
 			assertEquals(2, people.get(0).addresses.size());
 
-			r = client.doGet("/addressBook/people?s=@((age=d))");
+			r = client.doGet("/addressBook/people?o=age-");
 			people = r.getResponse(PersonList.class);
 			assertTrue(people.get(0).getAge() > 10);
-			r = client.doGet("/addressBook/people?s=@(age)");
+			r = client.doGet("/addressBook/people?o=age");
 			people = r.getResponse(PersonList.class);
 			assertTrue(people.get(0).getAge() > 10);
-			r = client.doGet("/addressBook/people?s=@((age=a))");
+			r = client.doGet("/addressBook/people?o=age+");
 			people = r.getResponse(PersonList.class);
 			assertTrue(people.get(0).getAge() > 10);
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html
----------------------------------------------------------------------
diff --git a/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html b/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html
index 77e23dc..36d8e9f 100755
--- a/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html
+++ b/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html
@@ -653,14 +653,14 @@
 					<ul>
 			 			<li><l>$R{attribute.X}</l> - Value returned by {@link org.apache.juneau.rest.RestRequest#getAttribute(String)} converted to a string.
 			 			<li><l>$R{contextPath}</l> - Value returned by {@link org.apache.juneau.rest.RestRequest#getContextPath()}.
-			 			<li><l>$R{formData.X}</l> - Value returned by {@link org.apache.juneau.rest.RequestFormData#getFirst(String)}.
-			 			<li><l>$R{header.X}</l> - Value returned by {@link org.apache.juneau.rest.RequestHeaders#getFirst(String)}.
+			 			<li><l>$R{formData.X}</l> - Value returned by {@link org.apache.juneau.rest.RequestFormData#getString(String)}.
+			 			<li><l>$R{header.X}</l> - Value returned by {@link org.apache.juneau.rest.RequestHeaders#getString(String)}.
 			 			<li><l>$R{method}</l> - Value returned by {@link org.apache.juneau.rest.RestRequest#getMethod()}.
 			 			<li><l>$R{methodSummary}</l> - Value returned by {@link org.apache.juneau.rest.RestRequest#getMethodSummary()}.
 			 			<li><l>$R{methodDescription}</l> - Value returned by {@link org.apache.juneau.rest.RestRequest#getMethodDescription()}.
 			 			<li><l>$R{path.X}</l> - Value returned by {@link org.apache.juneau.rest.RequestPathMatch#get(Object)}.
 			 			<li><l>$R{pathInfo}</l> - Value returned by {@link org.apache.juneau.rest.RestRequest#getPathInfo()}.
-			 			<li><l>$R{query.X}</l> - Value returned by {@link org.apache.juneau.rest.RequestQuery#getFirst(String)}.
+			 			<li><l>$R{query.X}</l> - Value returned by {@link org.apache.juneau.rest.RequestQuery#getString(String)}.
 			 			<li><l>$R{requestParentURI}</l> - Value returned by {@link org.apache.juneau.UriContext#getRootRelativePathInfoParent()}.
 			 			<li><l>$R{requestURI}</l> - Value returned by {@link org.apache.juneau.rest.RestRequest#getRequestURI()}.
 			 			<li><l>$R{servletDescription}</l> - Value returned by {@link org.apache.juneau.rest.RestRequest#getServletDescription()}.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/FormDataResource.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/FormDataResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/FormDataResource.java
index d83b821..34936b7 100644
--- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/FormDataResource.java
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/FormDataResource.java
@@ -44,9 +44,9 @@ public class FormDataResource extends RestServletDefault {
 	@RestMethod(name="POST", path="/defaultFormData", defaultFormData={"f1:1","f2=2"," f3 : 3 "})
 	public ObjectMap defaultFormData(RequestFormData formData) {
 		return new ObjectMap()
-			.append("f1", formData.getFirst("f1"))
-			.append("f2", formData.getFirst("f2"))
-			.append("f3", formData.getFirst("f3"));
+			.append("f1", formData.getString("f1"))
+			.append("f2", formData.getString("f2"))
+			.append("f3", formData.getString("f3"));
 	}
 
 	@RestMethod(name="POST", path="/annotatedFormData")

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HeadersResource.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HeadersResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HeadersResource.java
index edb58f2..1949a96 100644
--- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HeadersResource.java
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HeadersResource.java
@@ -199,17 +199,17 @@ public class HeadersResource extends RestServlet {
 	@RestMethod(name="GET", path="/defaultRequestHeaders", defaultRequestHeaders={"H1:1","H2=2"," H3 : 3 "})
 	public ObjectMap defaultRequestHeaders(RequestHeaders headers) {
 		return new ObjectMap()
-			.append("h1", headers.getFirst("H1"))
-			.append("h2", headers.getFirst("H2"))
-			.append("h3", headers.getFirst("H3"));
+			.append("h1", headers.getString("H1"))
+			.append("h2", headers.getString("H2"))
+			.append("h3", headers.getString("H3"));
 	}
 
 	@RestMethod(name="GET", path="/defaultRequestHeadersCaseInsensitive", defaultRequestHeaders={"H1:1","H2=2"," H3 : 3 "})
 	public ObjectMap defaultRequestHeadersCaseInsensitive(RequestHeaders headers) {
 		return new ObjectMap()
-			.append("h1", headers.getFirst("h1"))
-			.append("h2", headers.getFirst("h2"))
-			.append("h3", headers.getFirst("h3"));
+			.append("h1", headers.getString("h1"))
+			.append("h2", headers.getString("h2"))
+			.append("h3", headers.getString("h3"));
 	}
 
 	@RestMethod(name="GET", path="/annotatedHeaders")

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HtmlPropertiesResource.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HtmlPropertiesResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HtmlPropertiesResource.java
index e75ea5a..8cfc1a1 100644
--- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HtmlPropertiesResource.java
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/HtmlPropertiesResource.java
@@ -41,8 +41,7 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		path="/Normal",
 		htmldoc=@HtmlDoc(
 			title="Normal-title",
-			description="Normal-text",
-			links="{link:'Normal-links'}"
+			description="Normal-text"
 		)
 	)
 	public static class Normal extends RestServletDefault {
@@ -62,8 +61,7 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 			path="/test2",
 			htmldoc=@HtmlDoc(
 				title="Normal.test2-title",
-				description="Normal.test2-text",
-				links="{link:'Normal.test2-links'}"
+				description="Normal.test2-text"
 			)
 		)
 		public String test2() {
@@ -77,7 +75,6 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		public String test3(RestResponse res) {
 			res.setHtmlTitle("Normal.test3-title");
 			res.setHtmlDescription("Normal.test3-text");
-			res.setHtmlLinks("{link:'Normal.test3-links'}");
 			return "OK";
 		}
 
@@ -88,7 +85,6 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		public String test4(RestResponse res) {
 			res.setProperty(HtmlDocSerializerContext.HTMLDOC_title, "Normal.test4-title");
 			res.setProperty(HtmlDocSerializerContext.HTMLDOC_description, "Normal.test4-text");
-			res.setProperty(HtmlDocSerializerContext.HTMLDOC_links, "{link:'Normal.test4-links'}");
 			return "OK";
 		}
 	}
@@ -102,7 +98,6 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		public void init(RestConfig config) throws Exception {
 			config.setHtmlTitle("NormalInit-title");
 			config.setHtmlDescription("NormalInit-text");
-			config.setHtmlLinks("{link:'NormalInit-links'}");
 			super.init(config);
 		}
 
@@ -121,8 +116,7 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 			path="/test2",
 			htmldoc=@HtmlDoc(
 				title="NormalInit.test2-title",
-				description="NormalInit.test2-text",
-				links="{link:'NormalInit.test2-links'}"
+				description="NormalInit.test2-text"
 			)
 		)
 		public String test2() {
@@ -136,7 +130,6 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		public String test3(RestResponse res) {
 			res.setHtmlTitle("NormalInit.test3-title");
 			res.setHtmlDescription("NormalInit.test3-text");
-			res.setHtmlLinks("{link:'NormalInit.test3-links'}");
 			return "OK";
 		}
 
@@ -147,7 +140,6 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		public String test4(RestResponse res) {
 			res.setProperty(HtmlDocSerializerContext.HTMLDOC_title, "NormalInit.test4-title");
 			res.setProperty(HtmlDocSerializerContext.HTMLDOC_description, "NormalInit.test4-text");
-			res.setProperty(HtmlDocSerializerContext.HTMLDOC_links, "{link:'NormalInit.test4-links'}");
 			return "OK";
 		}
 	}
@@ -219,8 +211,7 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 			path="/test2",
 			htmldoc=@HtmlDoc(
 				title="NormalSubclassed1.test2-title",
-				description="NormalSubclassed1.test2-text",
-				links="{link:'NormalSubclassed1.test2-links'}"
+				description="NormalSubclassed1.test2-text"
 			)
 		)
 		public String test2() {
@@ -232,8 +223,7 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		path="/NormalSubclassed2",
 		htmldoc=@HtmlDoc(
 			title="NormalSubclassed2-title",
-			description="NormalSubclassed2-text",
-			links="{link:'NormalSubclassed2-links'}"
+			description="NormalSubclassed2-text"
 		)
 	)
 	public static class NormalSubclassed2 extends Normal {
@@ -255,8 +245,7 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 			path="/test2",
 			htmldoc=@HtmlDoc(
 				title="NormalSubclassed2.test2-title",
-				description="NormalSubclassed2.test2-text",
-				links="{link:'NormalSubclassed2.test2-links'}"
+				description="NormalSubclassed2.test2-text"
 			)
 		)
 		public String test2() {
@@ -269,8 +258,7 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		messages="HtmlPropertiesResource",
 		htmldoc=@HtmlDoc(
 			title="$L{pageTitle}",
-			description="$L{pageText}",
-			links="$L{pageLinks}"
+			description="$L{pageText}"
 		)
 	)
 	public static class LocalizedExplicit extends RestServletDefault {
@@ -289,7 +277,7 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		@RestMethod(
 			path="/test2",
 			htmldoc=@HtmlDoc(
-				title="$L{test2.pageTitle}", description="$L{test2.pageText}", links="$L{test2.pageLinks}"
+				title="$L{test2.pageTitle}", description="$L{test2.pageText}"
 			)
 		)
 		public String test2() {
@@ -303,7 +291,6 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		public String test3(RestResponse res) {
 			res.setHtmlTitle("$L{test3.pageTitle}");
 			res.setHtmlDescription("$L{test3.pageText}");
-			res.setHtmlLinks("$L{test3.pageLinks}");
 			return "OK";
 		}
 
@@ -314,7 +301,6 @@ public class HtmlPropertiesResource extends RestServletGroupDefault {
 		public String test4(RestResponse res) {
 			res.setProperty(HtmlDocSerializerContext.HTMLDOC_title, "$L{test4.pageTitle}");
 			res.setProperty(HtmlDocSerializerContext.HTMLDOC_description, "$L{test4.pageText}");
-			res.setProperty(HtmlDocSerializerContext.HTMLDOC_links, "$L{test4.pageLinks}");
 			return "OK";
 		}
 	}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java
index 0e943d4..c769089 100644
--- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java
@@ -45,14 +45,14 @@ public class OverlappingMethodsResource extends RestServletDefault {
 	public static class Test1Guard extends RestGuard {
 		@Override /* RestGuard */
 		public boolean isRequestAllowed(RestRequest req) {
-			return req.getQuery().getFirst("t1","").equals("1");
+			return req.getQuery().getString("t1","").equals("1");
 		}
 	}
 
 	public static class Test2Guard extends RestGuard {
 		@Override /* RestGuard */
 		public boolean isRequestAllowed(RestRequest req) {
-			return req.getQuery().getFirst("t2","").equals("2");
+			return req.getQuery().getString("t2","").equals("2");
 		}
 	}
 
@@ -77,14 +77,14 @@ public class OverlappingMethodsResource extends RestServletDefault {
 	public static class Test3aMatcher extends RestMatcher {
 		@Override /* RestMatcher */
 		public boolean matches(RestRequest req) {
-			return req.getQuery().getFirst("t1","").equals("1");
+			return req.getQuery().getString("t1","").equals("1");
 		}
 	}
 
 	public static class Test3bMatcher extends RestMatcher {
 		@Override /* RestMatcher */
 		public boolean matches(RestRequest req) {
-			return req.getQuery().getFirst("t2","").equals("2");
+			return req.getQuery().getString("t2","").equals("2");
 		}
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
index 3d49776..2ebcdaf 100644
--- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
@@ -124,7 +124,7 @@ public class ParamsResource extends RestServletDefault {
 	@RestMethod(name="GET", path="/testParamGet/*")
 	public String testParamGet(RestRequest req, @Query("p1") String p1, @Query("p2") int p2) throws Exception {
 		RequestQuery q = req.getQuery();
-		return "p1=["+p1+","+req.getQuery().getFirst("p1")+","+q.get("p1", String.class)+"],p2=["+p2+","+q.getFirst("p2")+","+q.get("p2", int.class)+"]";
+		return "p1=["+p1+","+req.getQuery().getString("p1")+","+q.get("p1", String.class)+"],p2=["+p2+","+q.getString("p2")+","+q.get("p2", int.class)+"]";
 	}
 
 	//====================================================================================================
@@ -133,7 +133,7 @@ public class ParamsResource extends RestServletDefault {
 	@RestMethod(name="POST", path="/testParamPost/*")
 	public String testParamPost(RestRequest req, @FormData("p1") String p1, @FormData("p2") int p2) throws Exception {
 		RequestFormData f = req.getFormData();
-		return "p1=["+p1+","+req.getFormData().getFirst("p1")+","+f.get("p1", String.class)+"],p2=["+p2+","+req.getFormData().getFirst("p2")+","+f.get("p2", int.class)+"]";
+		return "p1=["+p1+","+req.getFormData().getString("p1")+","+f.get("p1", String.class)+"],p2=["+p2+","+req.getFormData().getString("p2")+","+f.get("p2", int.class)+"]";
 	}
 
 	//====================================================================================================
@@ -142,7 +142,7 @@ public class ParamsResource extends RestServletDefault {
 	@RestMethod(name="GET", path="/testQParamGet/*")
 	public String testQParamGet(RestRequest req, @Query("p1") String p1, @Query("p2") int p2) throws Exception {
 		RequestQuery q = req.getQuery();
-		return "p1=["+p1+","+req.getQuery().getFirst("p1")+","+q.get("p1", String.class)+"],p2=["+p2+","+q.getFirst("p2")+","+q.get("p2", int.class)+"]";
+		return "p1=["+p1+","+req.getQuery().getString("p1")+","+q.get("p1", String.class)+"],p2=["+p2+","+q.getString("p2")+","+q.get("p2", int.class)+"]";
 	}
 
 	//====================================================================================================
@@ -151,7 +151,7 @@ public class ParamsResource extends RestServletDefault {
 	@RestMethod(name="POST", path="/testQParamPost/*")
 	public String testQParamPost(RestRequest req, @Query("p1") String p1, @Query("p2") int p2) throws Exception {
 		RequestQuery q = req.getQuery();
-		return "p1=["+p1+","+req.getQuery().getFirst("p1")+","+q.get("p1", String.class)+"],p2=["+p2+","+q.getFirst("p2")+","+q.get("p2", int.class)+"]";
+		return "p1=["+p1+","+req.getQuery().getString("p1")+","+q.get("p1", String.class)+"],p2=["+p2+","+q.getString("p2")+","+q.get("p2", int.class)+"]";
 	}
 
 	//====================================================================================================
@@ -160,7 +160,7 @@ public class ParamsResource extends RestServletDefault {
 	@RestMethod(name="GET", path="/testPlainParamGet/*")
 	public String testPlainParamGet(RestRequest req, @Query(value="p1",format="PLAIN") String p1) throws Exception {
 		RequestQuery q = req.getQuery();
-		return "p1=["+p1+","+req.getQuery().getFirst("p1")+","+q.get("p1", String.class)+"]";
+		return "p1=["+p1+","+req.getQuery().getString("p1")+","+q.get("p1", String.class)+"]";
 	}
 
 	//====================================================================================================
@@ -169,7 +169,7 @@ public class ParamsResource extends RestServletDefault {
 	@RestMethod(name="POST", path="/testPlainParamPost/*")
 	public String testPlainParamPost(RestRequest req, @FormData(value="p1",format="PLAIN") String p1) throws Exception {
 		RequestFormData f = req.getFormData();
-		return "p1=["+p1+","+req.getFormData().getFirst("p1")+","+f.get("p1", String.class)+"]";
+		return "p1=["+p1+","+req.getFormData().getString("p1")+","+f.get("p1", String.class)+"]";
 	}
 
 	//====================================================================================================
@@ -178,7 +178,7 @@ public class ParamsResource extends RestServletDefault {
 	@RestMethod(name="GET", path="/testPlainQParamGet/*")
 	public String testPlainQParamGet(RestRequest req, @Query(value="p1",format="PLAIN") String p1) throws Exception {
 		RequestQuery q = req.getQuery();
-		return "p1=["+p1+","+req.getQuery().getFirst("p1")+","+q.get("p1", String.class)+"]";
+		return "p1=["+p1+","+req.getQuery().getString("p1")+","+q.get("p1", String.class)+"]";
 	}
 
 	//====================================================================================================
@@ -187,7 +187,7 @@ public class ParamsResource extends RestServletDefault {
 	@RestMethod(name="POST", path="/testPlainQParamPost/*")
 	public String testPlainQParamPost(RestRequest req, @Query(value="p1",format="PLAIN") String p1) throws Exception {
 		RequestQuery q = req.getQuery();
-		return "p1=["+p1+","+req.getQuery().getFirst("p1")+","+q.get("p1", String.class)+"]";
+		return "p1=["+p1+","+req.getQuery().getString("p1")+","+q.get("p1", String.class)+"]";
 	}
 
 	//====================================================================================================

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/QueryResource.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/QueryResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/QueryResource.java
index 4c33538..db470ac 100644
--- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/QueryResource.java
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/QueryResource.java
@@ -32,9 +32,9 @@ public class QueryResource extends RestServletDefault {
 	@RestMethod(name="GET", path="/defaultQuery", defaultQuery={"f1:1","f2=2"," f3 : 3 "})
 	public ObjectMap defaultQuery(RequestQuery query) {
 		return new ObjectMap()
-			.append("f1", query.getFirst("f1"))
-			.append("f2", query.getFirst("f2"))
-			.append("f3", query.getFirst("f3"));
+			.append("f1", query.getString("f1"))
+			.append("f2", query.getString("f2"))
+			.append("f3", query.getString("f3"));
 	}
 
 	@RestMethod(name="GET", path="/annotatedQuery")

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest-test/src/main/resources/org/apache/juneau/rest/test/HtmlPropertiesResource.properties
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/resources/org/apache/juneau/rest/test/HtmlPropertiesResource.properties b/juneau-rest-test/src/main/resources/org/apache/juneau/rest/test/HtmlPropertiesResource.properties
index a216564..a35bfe7 100644
--- a/juneau-rest-test/src/main/resources/org/apache/juneau/rest/test/HtmlPropertiesResource.properties
+++ b/juneau-rest-test/src/main/resources/org/apache/juneau/rest/test/HtmlPropertiesResource.properties
@@ -14,24 +14,18 @@
 
 LocalizedExplicit.pageTitle = LocalizedExplicit.nls.pageTitle
 LocalizedExplicit.pageText = LocalizedExplicit.nls.pageText
-LocalizedExplicit.pageLinks = {link:'LocalizedExplicit.nls.pageLinks'}
 
 LocalizedExplicit.test2.pageTitle = LocalizedExplicit.test2.nls.pageTitle
 LocalizedExplicit.test2.pageText = LocalizedExplicit.test2.nls.pageText
-LocalizedExplicit.test2.pageLinks = {link:'LocalizedExplicit.test2.nls.pageLinks'}
 
 LocalizedExplicit.test3.pageTitle = LocalizedExplicit.test3.nls.pageTitle
 LocalizedExplicit.test3.pageText = LocalizedExplicit.test3.nls.pageText
-LocalizedExplicit.test3.pageLinks = {link:'LocalizedExplicit.test3.nls.pageLinks'}
 
 LocalizedExplicit.test4.pageTitle = LocalizedExplicit.test4.nls.pageTitle
 LocalizedExplicit.test4.pageText = LocalizedExplicit.test4.nls.pageText
-LocalizedExplicit.test4.pageLinks = {link:'LocalizedExplicit.test4.nls.pageLinks'}
 
 LocalizedImplicit.pageTitle = LocalizedImplicit.nls.pageTitle
 LocalizedImplicit.pageText = LocalizedImplicit.nls.pageText
-LocalizedImplicit.pageLinks = {link:'LocalizedImplicit.nls.pageLinks'}
 
 LocalizedImplicit.test2.pageTitle = LocalizedImplicit.test2.nls.pageTitle
 LocalizedImplicit.test2.pageText = LocalizedImplicit.test2.nls.pageText
-LocalizedImplicit.test2.pageLinks = {link:'LocalizedImplicit.test2.nls.pageLinks'}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/HtmlPropertiesTest.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/HtmlPropertiesTest.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/HtmlPropertiesTest.java
index acf2b8c..a9df966 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/HtmlPropertiesTest.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/HtmlPropertiesTest.java
@@ -31,7 +31,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/Normal/test1").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("Normal-title"));
 		assertTrue(s.contains("Normal-text"));
-		assertTrue(s.contains("Normal-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -43,7 +42,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/Normal/test2").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("Normal.test2-title"));
 		assertTrue(s.contains("Normal.test2-text"));
-		assertTrue(s.contains("Normal.test2-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -55,7 +53,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/Normal/test3").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("Normal.test3-title"));
 		assertTrue(s.contains("Normal.test3-text"));
-		assertTrue(s.contains("Normal.test3-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -67,7 +64,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/Normal/test4").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("Normal.test4-title"));
 		assertTrue(s.contains("Normal.test4-text"));
-		assertTrue(s.contains("Normal.test4-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -79,7 +75,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/NormalInit/test1").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("NormalInit-title"));
 		assertTrue(s.contains("NormalInit-text"));
-		assertTrue(s.contains("NormalInit-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -91,7 +86,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/NormalInit/test2").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("NormalInit.test2-title"));
 		assertTrue(s.contains("NormalInit.test2-text"));
-		assertTrue(s.contains("NormalInit.test2-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -103,7 +97,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/NormalInit/test3").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("NormalInit.test3-title"));
 		assertTrue(s.contains("NormalInit.test3-text"));
-		assertTrue(s.contains("NormalInit.test3-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -115,7 +108,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/NormalInit/test4").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("NormalInit.test4-title"));
 		assertTrue(s.contains("NormalInit.test4-text"));
-		assertTrue(s.contains("NormalInit.test4-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -171,7 +163,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/NormalSubclassed1/test1").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("Normal-title"));
 		assertTrue(s.contains("Normal-text"));
-		assertTrue(s.contains("Normal-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -183,7 +174,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/NormalSubclassed1/test2").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("NormalSubclassed1.test2-title"));
 		assertTrue(s.contains("NormalSubclassed1.test2-text"));
-		assertTrue(s.contains("NormalSubclassed1.test2-links"));
 
 	}
 
@@ -196,7 +186,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/NormalSubclassed2/test1").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("NormalSubclassed2-title"));
 		assertTrue(s.contains("NormalSubclassed2-text"));
-		assertTrue(s.contains("NormalSubclassed2-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -208,7 +197,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/NormalSubclassed2/test2").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("NormalSubclassed2.test2-title"));
 		assertTrue(s.contains("NormalSubclassed2.test2-text"));
-		assertTrue(s.contains("NormalSubclassed2.test2-links"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -220,7 +208,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/LocalizedExplicit/test1").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("LocalizedExplicit.nls.pageTitle"));
 		assertTrue(s.contains("LocalizedExplicit.nls.pageText"));
-		assertTrue(s.contains("LocalizedExplicit.nls.pageLinks"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -232,7 +219,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/LocalizedExplicit/test2").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("LocalizedExplicit.test2.nls.pageTitle"));
 		assertTrue(s.contains("LocalizedExplicit.test2.nls.pageText"));
-		assertTrue(s.contains("LocalizedExplicit.test2.nls.pageLinks"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -244,7 +230,6 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/LocalizedExplicit/test3").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("LocalizedExplicit.test3.nls.pageTitle"));
 		assertTrue(s.contains("LocalizedExplicit.test3.nls.pageText"));
-		assertTrue(s.contains("LocalizedExplicit.test3.nls.pageLinks"));
 	}
 
 	//----------------------------------------------------------------------------------------------------
@@ -256,6 +241,5 @@ public class HtmlPropertiesTest extends RestTestcase {
 		String s = client.doGet("/testHtmlProperties/LocalizedExplicit/test4").accept("text/html").getResponseAsString();
 		assertTrue(s.contains("LocalizedExplicit.test4.nls.pageTitle"));
 		assertTrue(s.contains("LocalizedExplicit.test4.nls.pageText"));
-		assertTrue(s.contains("LocalizedExplicit.test4.nls.pageLinks"));
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
index a2f0777..725abce 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
@@ -36,7 +36,7 @@ public class TestMicroservice {
 	static URI microserviceURI;
 
 	// Reusable HTTP clients that get created and shut down with the microservice.
-	public static RestClient DEFAULT_CLIENT;
+	public static RestClient DEFAULT_CLIENT, DEFAULT_CLIENT_DEBUG;
 	public static RestClient DEFAULT_CLIENT_PLAINTEXT;
 
 	/**
@@ -56,6 +56,7 @@ public class TestMicroservice {
 				);
 			microserviceURI = microservice.start().getURI();
 			DEFAULT_CLIENT = client().build();
+			DEFAULT_CLIENT_DEBUG = client().debug().build();
 			DEFAULT_CLIENT_PLAINTEXT = client(PlainTextSerializer.class, PlainTextParser.class).build();
 			return true;
 		} catch (Throwable e) {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
index bcb00db..2563442 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
@@ -67,6 +67,7 @@ class CallMethod implements Comparable<CallMethod>  {
 	private final org.apache.juneau.rest.annotation.Parameter[] parameters;
 	private final Response[] responses;
 	private final RestContext context;
+	private final BeanContext beanContext;
 	private final String htmlTitle, htmlDescription, htmlBranding, htmlHeader, htmlLinks, htmlNav, htmlAside,
 		htmlFooter, htmlCss, htmlCssUrl, htmlNoResultsMessage;
 	private final boolean htmlNoWrap;
@@ -89,6 +90,7 @@ class CallMethod implements Comparable<CallMethod>  {
 		this.encoders = b.encoders;
 		this.urlEncodingParser = b.urlEncodingParser;
 		this.urlEncodingSerializer = b.urlEncodingSerializer;
+		this.beanContext = b.beanContext;
 		this.properties = b.properties;
 		this.defaultRequestHeaders = b.defaultRequestHeaders;
 		this.defaultQuery = b.defaultQuery;
@@ -133,6 +135,7 @@ class CallMethod implements Comparable<CallMethod>  {
 		private EncoderGroup encoders;
 		private UrlEncodingParser urlEncodingParser;
 		private UrlEncodingSerializer urlEncodingSerializer;
+		private BeanContext beanContext;
 		private ObjectMap properties;
 		private Map<String,String> defaultRequestHeaders, defaultQuery, defaultFormData;
 		private boolean plainParams, deprecated;
@@ -165,6 +168,7 @@ class CallMethod implements Comparable<CallMethod>  {
 				parsers = context.getParsers();
 				urlEncodingSerializer = context.getUrlEncodingSerializer();
 				urlEncodingParser = context.getUrlEncodingParser();
+				beanContext = context.getBeanContext();
 				encoders = context.getEncoders();
 				properties = context.getProperties();
 				widgets = new HashMap<String,Widget>(context.getWidgets());
@@ -358,8 +362,10 @@ class CallMethod implements Comparable<CallMethod>  {
 
 				params = context.findParams(method, plainParams, pathPattern);
 
-				if (sgb != null)
+				if (sgb != null) {
 					serializers = sgb.build();
+					beanContext = serializers.getBeanContext();
+				}
 				if (pgb != null)
 					parsers = pgb.build();
 				if (uepb != null)
@@ -746,7 +752,7 @@ class CallMethod implements Comparable<CallMethod>  {
 
 		ObjectMap requestProperties = createRequestProperties(properties, req);
 		req.init(method, requestProperties, defaultRequestHeaders, defaultQuery, defaultFormData, defaultCharset,
-			serializers, parsers, urlEncodingParser, encoders, widgets);
+			serializers, parsers, urlEncodingParser, beanContext, encoders, widgets);
 		res.init(requestProperties, defaultCharset, serializers, urlEncodingSerializer, encoders);
 
 		// Class-level guards
@@ -798,7 +804,7 @@ class CallMethod implements Comparable<CallMethod>  {
 			if (res.hasOutput()) {
 				output = res.getOutput();
 				for (RestConverter converter : converters)
-					output = converter.convert(req, output, context.getBeanContext().getClassMetaForObject(output));
+					output = converter.convert(req, output, beanContext.getClassMetaForObject(output));
 				res.setOutput(output);
 			}
 		} catch (IllegalArgumentException e) {
@@ -883,8 +889,18 @@ class CallMethod implements Comparable<CallMethod>  {
 						return htmlHeader == null ? null : req.resolveVars(htmlHeader);
 					if (k.equals(HTMLDOC_branding))
 						return htmlBranding == null ? null : req.resolveVars(htmlBranding);
-					if (k.equals(HTMLDOC_links))
-						return htmlLinks == null ? null : req.resolveVars(htmlLinks);
+					if (k.equals(HTMLDOC_links)) {
+						if (htmlLinks == null)
+							return null;
+						try {
+							ObjectMap m = new ObjectMap(htmlLinks), m2 = new ObjectMap();
+							for (Map.Entry<String,Object> e : m.entrySet())
+								m2.put(req.resolveVars(e.getKey()), req.resolveVars(StringUtils.toString(e.getValue())));
+							return m2;
+						} catch (ParseException e) {
+							throw new RuntimeException(e);
+						}
+					}
 					if (k.equals(HTMLDOC_nav))
 						return htmlNav == null ? null : req.resolveVars(htmlNav);
 					if (k.equals(HTMLDOC_aside))

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/RequestFormData.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RequestFormData.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestFormData.java
index 5a2ed4d..7de7cdb 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RequestFormData.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestFormData.java
@@ -94,7 +94,7 @@ public class RequestFormData extends LinkedHashMap<String,String[]> {
 	 * @param name The form data parameter name.
 	 * @return The parameter value, or <jk>null</jk> if parameter does not exist.
 	 */
-	public String getFirst(String name) {
+	public String getString(String name) {
 		String[] v = get(name);
 		if (v == null || v.length == 0)
 			return null;
@@ -109,18 +109,62 @@ public class RequestFormData extends LinkedHashMap<String,String[]> {
 	}
 
 	/**
-	 * Same as {@link #getFirst(String)} except returns a default value if <jk>null</jk> or empty.
+	 * Same as {@link #getString(String)} except returns a default value if <jk>null</jk> or empty.
 	 *
 	 * @param name The form data parameter name.
 	 * @param def The default value.
-	 * @return The parameter value, or the default value if <jk>null</jk> or empty.
+	 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty.
 	 */
-	public String getFirst(String name, String def) {
-		String s = getFirst(name);
+	public String getString(String name, String def) {
+		String s = getString(name);
 		return StringUtils.isEmpty(s) ? def : s;
 	}
 
 	/**
+	 * Same as {@link #getString(String)} but converts the value to an integer.
+	 *
+	 * @param name The form data parameter name.
+	 * @return The parameter value, or <code>0</code> if parameter does not exist or is <jk>null</jk> or empty.
+	 */
+	public int getInt(String name) {
+		return getInt(name, 0);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to an integer.
+	 *
+	 * @param name The form data parameter name.
+	 * @param def The default value.
+	 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty.
+	 */
+	public int getInt(String name, int def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Integer.parseInt(s);
+	}
+
+	/**
+	 * Same as {@link #getString(String)} but converts the value to a boolean.
+	 *
+	 * @param name The form data parameter name.
+	 * @return The parameter value, or <jk>false</jk> if parameter does not exist or is <jk>null</jk> or empty.
+	 */
+	public boolean getBoolean(String name) {
+		return getBoolean(name, false);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to a boolean.
+	 *
+	 * @param name The form data parameter name.
+	 * @param def The default value.
+	 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty.
+	 */
+	public boolean getBoolean(String name, boolean def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s);
+	}
+
+	/**
 	 * Returns the specified form data parameter value converted to a POJO using the
 	 * 	{@link UrlEncodingParser} registered with this servlet.
 	 * <p>
@@ -245,7 +289,7 @@ public class RequestFormData extends LinkedHashMap<String,String[]> {
 
 	/* Workhorse method */
 	<T> T parse(String name, T def, ClassMeta<T> cm) throws ParseException {
-		String val = getFirst(name);
+		String val = getString(name);
 		if (val == null)
 			return def;
 		return parseValue(val, cm);
@@ -253,7 +297,7 @@ public class RequestFormData extends LinkedHashMap<String,String[]> {
 
 	/* Workhorse method */
 	<T> T parse(String name, ClassMeta<T> cm) throws ParseException {
-		String val = getFirst(name);
+		String val = getString(name);
 		if (cm.isPrimitive() && (val == null || val.isEmpty()))
 			return cm.getPrimitiveDefault();
 		return parseValue(val, cm);

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/RequestHeaders.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RequestHeaders.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestHeaders.java
index 87fbb23..ea2d9de 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RequestHeaders.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestHeaders.java
@@ -104,7 +104,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @param name The header name.
 	 * @return The header value, or <jk>null</jk> if it doesn't exist.
 	 */
-	public String getFirst(String name) {
+	public String getString(String name) {
 		String[] v = null;
 		if (queryParams != null)
 			v = queryParams.get(name);
@@ -124,12 +124,56 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @param def The default value to return if the header value isn't found.
 	 * @return The header value, or the default value if the header isn't present.
 	 */
-	public String getFirst(String name, String def) {
-		String s = getFirst(name);
+	public String getString(String name, String def) {
+		String s = getString(name);
 		return StringUtils.isEmpty(s) ? def : s;
 	}
 
 	/**
+	 * Same as {@link #getString(String)} but converts the value to an integer.
+	 *
+	 * @param name The HTTP header name.
+	 * @return The header value, or the default value if the header isn't present.
+	 */
+	public int getInt(String name) {
+		return getInt(name, 0);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to an integer.
+	 *
+	 * @param name The HTTP header name.
+	 * @param def The default value to return if the header value isn't found.
+	 * @return The header value, or the default value if the header isn't present.
+	 */
+	public int getInt(String name, int def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Integer.parseInt(s);
+	}
+
+	/**
+	 * Same as {@link #getString(String)} but converts the value to a boolean.
+	 *
+	 * @param name The HTTP header name.
+	 * @return The header value, or the default value if the header isn't present.
+	 */
+	public boolean getBoolean(String name) {
+		return getBoolean(name, false);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to a boolean.
+	 *
+	 * @param name The HTTP header name.
+	 * @param def The default value to return if the header value isn't found.
+	 * @return The header value, or the default value if the header isn't present.
+	 */
+	public boolean getBoolean(String name, boolean def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s);
+	}
+
+	/**
 	 * Sets a request header value.
 	 *
 	 * @param name The header name.
@@ -160,7 +204,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parameter value converted to the specified class type.
 	 */
 	public <T> T get(String name, Class<T> type) {
-		String h = getFirst(name);
+		String h = getString(name);
 		return beanSession.convertToType(h, type);
 	}
 
@@ -174,7 +218,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parameter value converted to the specified class type.
 	 */
 	public <T> T get(String name, T def, Class<T> type) {
-		String h = getFirst(name);
+		String h = getString(name);
 		if (h == null)
 			return def;
 		return beanSession.convertToType(h, type);
@@ -204,7 +248,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 */
 	@SuppressWarnings("unchecked")
 	public <T> T get(String name, Type type, Type...args) throws ParseException {
-		String h = getFirst(name);
+		String h = getString(name);
 		return (T)parser.parse(PartType.HEADER, h, beanSession.getClassMeta(type, args));
 	}
 
@@ -244,7 +288,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 *
 	 */
 	public Accept getAccept() {
-		return Accept.forString(getFirst("Accept"));
+		return Accept.forString(getString("Accept"));
 	}
 
 	/**
@@ -260,7 +304,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Accept-Charset</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public AcceptCharset getAcceptCharset() {
-		return AcceptCharset.forString(getFirst("Accept-Charset"));
+		return AcceptCharset.forString(getString("Accept-Charset"));
 	}
 
 	/**
@@ -276,7 +320,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Accept-Encoding</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public AcceptEncoding getAcceptEncoding() {
-		return AcceptEncoding.forString(getFirst("Accept-Encoding"));
+		return AcceptEncoding.forString(getString("Accept-Encoding"));
 	}
 
 	/**
@@ -292,7 +336,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Accept-Language</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public AcceptLanguage getAcceptLanguage() {
-		return AcceptLanguage.forString(getFirst("Accept-Language"));
+		return AcceptLanguage.forString(getString("Accept-Language"));
 	}
 
 	/**
@@ -308,7 +352,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Authorization</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Authorization getAuthorization() {
-		return Authorization.forString(getFirst("Authorization"));
+		return Authorization.forString(getString("Authorization"));
 	}
 
 	/**
@@ -324,7 +368,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Cache-Control</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public CacheControl getCacheControl() {
-		return CacheControl.forString(getFirst("Cache-Control"));
+		return CacheControl.forString(getString("Cache-Control"));
 	}
 
 	/**
@@ -341,7 +385,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code></code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Connection getConnection() {
-		return Connection.forString(getFirst("Connection"));
+		return Connection.forString(getString("Connection"));
 	}
 
 	/**
@@ -357,7 +401,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Content-Length</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public ContentLength getContentLength() {
-		return ContentLength.forString(getFirst("Content-Length"));
+		return ContentLength.forString(getString("Content-Length"));
 	}
 
 	/**
@@ -373,7 +417,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Content-Type</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public ContentType getContentType() {
-		return ContentType.forString(getFirst("Content-Type"));
+		return ContentType.forString(getString("Content-Type"));
 	}
 
 	/**
@@ -389,7 +433,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Date</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Date getDate() {
-		return Date.forString(getFirst("Date"));
+		return Date.forString(getString("Date"));
 	}
 
 	/**
@@ -405,7 +449,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Expect</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Expect getExpect() {
-		return Expect.forString(getFirst("Expect"));
+		return Expect.forString(getString("Expect"));
 	}
 
 	/**
@@ -421,7 +465,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>From</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public From getFrom() {
-		return From.forString(getFirst("From"));
+		return From.forString(getString("From"));
 	}
 
 	/**
@@ -439,7 +483,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Host</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Host getHost() {
-		return Host.forString(getFirst("Host"));
+		return Host.forString(getString("Host"));
 	}
 
 	/**
@@ -456,7 +500,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>If-Match</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public IfMatch getIfMatch() {
-		return IfMatch.forString(getFirst("If-Match"));
+		return IfMatch.forString(getString("If-Match"));
 	}
 
 	/**
@@ -472,7 +516,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>If-Modified-Since</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public IfModifiedSince getIfModifiedSince() {
-		return IfModifiedSince.forString(getFirst("If-Modified-Since"));
+		return IfModifiedSince.forString(getString("If-Modified-Since"));
 	}
 
 	/**
@@ -488,7 +532,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>If-None-Match</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public IfNoneMatch getIfNoneMatch() {
-		return IfNoneMatch.forString(getFirst("If-None-Match"));
+		return IfNoneMatch.forString(getString("If-None-Match"));
 	}
 
 	/**
@@ -504,7 +548,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>If-Range</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public IfRange getIfRange() {
-		return IfRange.forString(getFirst("If-Range"));
+		return IfRange.forString(getString("If-Range"));
 	}
 
 	/**
@@ -520,7 +564,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>If-Unmodified-Since</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public IfUnmodifiedSince getIfUnmodifiedSince() {
-		return IfUnmodifiedSince.forString(getFirst("If-Unmodified-Since"));
+		return IfUnmodifiedSince.forString(getString("If-Unmodified-Since"));
 	}
 
 	/**
@@ -536,7 +580,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Max-Forwards</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public MaxForwards getMaxForwards() {
-		return MaxForwards.forString(getFirst("Max-Forwards"));
+		return MaxForwards.forString(getString("Max-Forwards"));
 	}
 
 	/**
@@ -552,7 +596,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Pragma</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Pragma getPragma() {
-		return Pragma.forString(getFirst("Pragma"));
+		return Pragma.forString(getString("Pragma"));
 	}
 
 	/**
@@ -568,7 +612,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Proxy-Authorization</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public ProxyAuthorization getProxyAuthorization() {
-		return ProxyAuthorization.forString(getFirst("Proxy-Authorization"));
+		return ProxyAuthorization.forString(getString("Proxy-Authorization"));
 	}
 
 	/**
@@ -584,7 +628,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Range</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Range getRange() {
-		return Range.forString(getFirst("Range"));
+		return Range.forString(getString("Range"));
 	}
 
 	/**
@@ -600,7 +644,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Referer</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Referer getReferer() {
-		return Referer.forString(getFirst("Referer"));
+		return Referer.forString(getString("Referer"));
 	}
 
 	/**
@@ -618,7 +662,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>TE</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public TE getTE() {
-		return TE.forString(getFirst("TE"));
+		return TE.forString(getString("TE"));
 	}
 
 	/**
@@ -629,7 +673,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The <code>Time-Zone</code> header value on the request, or <jk>null</jk> if not present.
 	 */
 	public TimeZone getTimeZone() {
-		String tz = getFirst("Time-Zone");
+		String tz = getString("Time-Zone");
 		if (tz != null)
 			return TimeZone.getTimeZone(tz);
 		return null;
@@ -648,7 +692,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>User-Agent</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public UserAgent getUserAgent() {
-		return UserAgent.forString(getFirst("User-Agent"));
+		return UserAgent.forString(getString("User-Agent"));
 	}
 
 	/**
@@ -664,7 +708,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Upgrade</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Upgrade getUpgrade() {
-		return Upgrade.forString(getFirst("Upgrade"));
+		return Upgrade.forString(getString("Upgrade"));
 	}
 
 	/**
@@ -680,7 +724,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Via</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Via getVia() {
-		return Via.forString(getFirst("Via"));
+		return Via.forString(getString("Via"));
 	}
 
 	/**
@@ -696,7 +740,7 @@ public class RequestHeaders extends TreeMap<String,String[]> {
 	 * @return The parsed <code>Warning</code> header on the request, or <jk>null</jk> if not found.
 	 */
 	public Warning getWarning() {
-		return Warning.forString(getFirst("Warning"));
+		return Warning.forString(getString("Warning"));
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/RequestQuery.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RequestQuery.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestQuery.java
index e97169e..d7d6cfe 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RequestQuery.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestQuery.java
@@ -24,6 +24,7 @@ import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.utils.*;
 
 /**
  * Represents the query parameters in an HTTP request.
@@ -81,11 +82,13 @@ public final class RequestQuery extends LinkedHashMap<String,String[]> {
 	 * Same as {@link HttpServletRequest#getParameter(String)} except only looks in the URL string, not parameters from URL-Encoded FORM posts.
 	 * <p>
 	 * This method can be used to retrieve a parameter without triggering the underlying servlet API to load and parse the request body.
+	 * <p>
+	 * If multiple query parameters have the same name, this returns only the first instance.
 	 *
 	 * @param name The URL parameter name.
 	 * @return The parameter value, or <jk>null</jk> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>.
 	 */
-	public String getFirst(String name) {
+	public String getString(String name) {
 		String[] v = get(name);
 		if (v == null || v.length == 0)
 			return null;
@@ -100,18 +103,62 @@ public final class RequestQuery extends LinkedHashMap<String,String[]> {
 	}
 
 	/**
-	 * Same as {@link #getFirst(String)} but returns the specified default value if the query parameter was not specified.
+	 * Same as {@link #getString(String)} but returns the specified default value if the query parameter was not specified.
 	 *
 	 * @param name The URL parameter name.
 	 * @param def The default value.
 	 * @return The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>.
 	 */
-	public String getFirst(String name, String def) {
-		String s = getFirst(name);
+	public String getString(String name, String def) {
+		String s = getString(name);
 		return StringUtils.isEmpty(s) ? def : s;
 	}
 
 	/**
+	 * Same as {@link #getString(String)} but converts the value to an integer.
+	 *
+	 * @param name The URL parameter name.
+	 * @return The parameter value, or <code>0</code> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>.
+	 */
+	public int getInt(String name) {
+		return getInt(name, 0);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to an integer.
+	 *
+	 * @param name The URL parameter name.
+	 * @param def The default value.
+	 * @return The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>.
+	 */
+	public int getInt(String name, int def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Integer.parseInt(s);
+	}
+
+	/**
+	 * Same as {@link #getString(String)} but converts the value to a boolean.
+	 *
+	 * @param name The URL parameter name.
+	 * @return The parameter value, or <jk>false</jk> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>.
+	 */
+	public boolean getBoolean(String name) {
+		return getBoolean(name, false);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to a boolean.
+	 *
+	 * @param name The URL parameter name.
+	 * @param def The default value.
+	 * @return The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>.
+	 */
+	public boolean getBoolean(String name, boolean def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s);
+	}
+
+	/**
 	 * Returns the specified query parameter value converted to a POJO.
 	 * <p>
 	 * This method can be used to retrieve a parameter without triggering the underlying servlet API to load and parse the request body.
@@ -261,9 +308,62 @@ public final class RequestQuery extends LinkedHashMap<String,String[]> {
 		return false;
 	}
 
+	/**
+	 * Locates the special search query arguments in the query and returns them as a {@link SearchArgs} object.
+	 * <p>
+	 * The query arguments are as follows:
+	 * <ul>
+	 * 	<li><js>"&s="</js> - A comma-delimited list of column-name/search-token pairs.
+	 * 		<br>Example: <js>"&s=column1=foo*,column2=*bar"</js>
+	 * 	<li><js>"&v="</js> - A comma-delimited list column names to view.
+	 * 		<br>Example: <js>"&v=column1,column2"</js>
+	 * 	<li><js>"&o="</js> - A comma-delimited list column names to sort by.
+	 * 		<br>Column names can be suffixed with <js>'-'</js> to indicate descending order.
+	 * 		<br>Example: <js>"&o=column1,column2-"</js>
+	 * 	<li><js>"&p="</js> - The zero-index row number of the first row to display.
+	 * 		<br>Example: <js>"&p=100"</js>
+	 * 	<li><js>"&l="</js> - The number of rows to return.
+	 * 		<br><code>0</code> implies return all rows.
+	 * 		<br>Example: <js>"&l=100"</js>
+	 * 	<li><js>"&i="</js> - The case-insensitive search flag.
+	 * 		<br>Example: <js>"&i=true"</js>
+	 * </ul>
+	 * <p>
+	 * Whitespace is trimmed in the parameters.
+	 *
+	 * @return A new {@link SearchArgs} object initialized with the special search query arguments.
+	 * 	<jk>null</jk> if no search arguments were found.
+	 */
+	public SearchArgs getSearchArgs() {
+		if (hasAny("s","v","o","p","l","i")) {
+			return new SearchArgs.Builder()
+				.search(getString("s"))
+				.view(getString("v"))
+				.sort(getString("o"))
+				.position(getInt("p"))
+				.limit(getInt("l"))
+				.ignoreCase(getBoolean("i"))
+				.build();
+		}
+		return null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the query parameters contains any of the specified names.
+	 *
+	 * @param paramNames The parameter names to check for.
+	 * @return <jk>true</jk> if the query parameters contains any of the specified names.
+	 */
+	public boolean hasAny(String...paramNames) {
+		for (String p : paramNames)
+			if (containsKey(p))
+				return true;
+		return false;
+	}
+
 	/* Workhorse method */
 	private <T> T parse(String name, T def, ClassMeta<T> cm) throws ParseException {
-		String val = getFirst(name);
+		String val = getString(name);
 		if (val == null)
 			return def;
 		return parseValue(val, cm);
@@ -271,7 +371,7 @@ public final class RequestQuery extends LinkedHashMap<String,String[]> {
 
 	/* Workhorse method */
 	private <T> T parse(String name, ClassMeta<T> cm) throws ParseException {
-		String val = getFirst(name);
+		String val = getString(name);
 		if (cm.isPrimitive() && (val == null || val.isEmpty()))
 			return cm.getPrimitiveDefault();
 		return parseValue(val, cm);

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestParamDefaults.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
index 03f4239..cc0c5b3 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
@@ -600,7 +600,7 @@ class RestParamDefaults {
 			if (multiPart)
 				return req.getFormData().getAll(name, type);
 			if (plainParams)
-				return bs.convertToType(req.getFormData().getFirst(name), bs.getClassMeta(type));
+				return bs.convertToType(req.getFormData().getString(name), bs.getClassMeta(type));
 			return req.getFormData().get(name, type);
 		}
 	}
@@ -622,7 +622,7 @@ class RestParamDefaults {
 			if (multiPart)
 				return req.getQuery().getAll(name, type);
 			if (plainParams)
-				return bs.convertToType(req.getQuery().getFirst(name), bs.getClassMeta(type));
+				return bs.convertToType(req.getQuery().getString(name), bs.getClassMeta(type));
 			return req.getQuery().get(name, type);
 		}
 	}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
index 755109f..faf062d 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -110,7 +110,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 			// Can be overridden through a "method" GET attribute.
 			String _method = super.getMethod();
 
-			String m = getQuery().getFirst("method");
+			String m = getQuery().getString("method");
 			if (context.allowMethodParam(m))
 				_method = m;
 
@@ -125,7 +125,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 			body = new RequestBody(this);
 
 			if (context.isAllowBodyParam()) {
-				String b = getQuery().getFirst("body");
+				String b = getQuery().getString("body");
 				if (b != null) {
 					headers.put("Content-Type", UonSerializer.DEFAULT.getResponseContentType());
 					body.load(b.getBytes(UTF8));
@@ -135,7 +135,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 			if (context.isAllowHeaderParams())
 				headers.setQueryParams(queryParams);
 
-			debug = "true".equals(getQuery().getFirst("debug", "false")) || "true".equals(getHeaders().getFirst("Debug", "false"));
+			debug = "true".equals(getQuery().getString("debug", "false")) || "true".equals(getHeaders().getString("Debug", "false"));
 
 			this.pathParams = new RequestPathMatch();
 
@@ -153,11 +153,11 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	final void init(Method javaMethod, ObjectMap properties, Map<String,String> defHeader,
 			Map<String,String> defQuery, Map<String,String> defFormData, String defaultCharset,
 			SerializerGroup mSerializers, ParserGroup mParsers, UrlEncodingParser mUrlEncodingParser,
-			EncoderGroup encoders, Map<String,Widget> widgets) {
+			BeanContext beanContext, EncoderGroup encoders, Map<String,Widget> widgets) {
 		this.javaMethod = javaMethod;
 		this.properties = properties;
 		this.urlEncodingParser = mUrlEncodingParser;
-		this.beanSession = urlEncodingParser.getBeanContext().createSession();
+		this.beanSession = beanContext.createSession();
 		this.pathParams
 			.setParser(urlEncodingParser)
 			.setBeanSession(beanSession);
@@ -253,7 +253,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 
 	@Override /* ServletRequest */
 	public String getHeader(String name) {
-		return getHeaders().getFirst(name);
+		return getHeaders().getString(name);
 	}
 
 	@Override /* ServletRequest */
@@ -306,7 +306,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 
 	@Override /* ServletRequest */
 	public Locale getLocale() {
-		String h = headers.getFirst("Accept-Language");
+		String h = headers.getString("Accept-Language");
 		if (h != null) {
 			MediaTypeRange[] mr = MediaTypeRange.parse(h);
 			if (mr.length > 0)
@@ -317,7 +317,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 
 	@Override /* ServletRequest */
 	public Enumeration<Locale> getLocales() {
-		String h = headers.getFirst("Accept-Language");
+		String h = headers.getString("Accept-Language");
 		if (h != null) {
 			MediaTypeRange[] mr = MediaTypeRange.parse(h);
 			if (mr.length > 0) {
@@ -349,12 +349,12 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	}
 
 	/**
-	 * Shortcut for calling <code>getQuery().getFirst(name)</code>.
+	 * Shortcut for calling <code>getQuery().getString(name)</code>.
 	 * @param name The query parameter name.
 	 * @return The query parameter value, or <jk>null<jk> if not found.
 	 */
 	public String getQuery(String name) {
-		return getQuery().getFirst(name);
+		return getQuery().getString(name);
 	}
 
 
@@ -389,12 +389,12 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	}
 
 	/**
-	 * Shortcut for calling <code>getFormData().getFirst(name)</code>.
+	 * Shortcut for calling <code>getFormData().getString(name)</code>.
 	 * @param name The form data parameter name.
 	 * @return The form data parameter value, or <jk>null<jk> if not found.
 	 */
 	public String getFormData(String name) {
-		return getFormData().getFirst(name);
+		return getFormData().getString(name);
 	}
 
 
@@ -613,7 +613,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 * @return <jk>true</jk> if {@code &amp;plainText=true} was specified as a URL parameter
 	 */
 	public boolean isPlainText() {
-		return "true".equals(getQuery().getFirst("plainText", "false"));
+		return "true".equals(getQuery().getString("plainText", "false"));
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java
index f84368e..6414db0 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java
@@ -40,8 +40,8 @@ public final class Introspectable implements RestConverter {
 	@Override /* RestConverter */
 	@SuppressWarnings({"unchecked", "rawtypes"})
 	public Object convert(RestRequest req, Object o, ClassMeta cm) throws RestException {
-		String method = req.getQuery().getFirst("invokeMethod");
-		String args = req.getQuery().getFirst("invokeArgs");
+		String method = req.getQuery().getString("invokeMethod");
+		String args = req.getQuery().getString("invokeArgs");
 		if (method == null)
 			return o;
 		try {


[3/3] incubator-juneau git commit: Add QueryWidget support.

Posted by ja...@apache.org.
Add QueryWidget 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/09590092
Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/09590092
Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/09590092

Branch: refs/heads/master
Commit: 095900928c782cf8c0f92b5f331b5f73425e8b6e
Parents: f836859
Author: JamesBognar <ja...@apache.org>
Authored: Sun Jun 18 15:42:16 2017 -0400
Committer: JamesBognar <ja...@apache.org>
Committed: Sun Jun 18 15:42:16 2017 -0400

----------------------------------------------------------------------
 .../org/apache/juneau/utils/PojoQueryTest.java  | 1314 +++++++++---------
 .../java/org/apache/juneau/BeanContext.java     |    2 +
 .../main/java/org/apache/juneau/BeanMap.java    |   15 +
 .../org/apache/juneau/BeanPropertyMeta.java     |   28 +-
 .../main/java/org/apache/juneau/ObjectList.java |    8 +-
 .../main/java/org/apache/juneau/ObjectMap.java  |    8 +-
 .../juneau/html/HtmlDocSerializerContext.java   |    6 +-
 .../juneau/html/HtmlDocSerializerSession.java   |    4 +-
 .../juneau/html/HtmlDocTemplateBasic.java       |   10 +-
 .../org/apache/juneau/internal/StringUtils.java |   39 +
 .../juneau/serializer/SerializerGroup.java      |   11 +
 .../java/org/apache/juneau/utils/PojoQuery.java |  219 +--
 .../org/apache/juneau/utils/SearchArgs.java     |  301 ++++
 juneau-core/src/main/javadoc/overview.html      |    1 +
 .../juneau/examples/rest/PetStoreResource.java  |   26 +-
 .../apache/juneau/examples/rest/PetStore.json   |    6 +-
 .../examples/rest/AddressBookResourceTest.java  |   16 +-
 .../org/apache/juneau/microservice/package.html |    6 +-
 .../juneau/rest/test/FormDataResource.java      |    6 +-
 .../juneau/rest/test/HeadersResource.java       |   12 +-
 .../rest/test/HtmlPropertiesResource.java       |   30 +-
 .../rest/test/OverlappingMethodsResource.java   |    8 +-
 .../apache/juneau/rest/test/ParamsResource.java |   16 +-
 .../apache/juneau/rest/test/QueryResource.java  |    6 +-
 .../rest/test/HtmlPropertiesResource.properties |    6 -
 .../juneau/rest/test/HtmlPropertiesTest.java    |   16 -
 .../juneau/rest/test/TestMicroservice.java      |    3 +-
 .../java/org/apache/juneau/rest/CallMethod.java |   26 +-
 .../org/apache/juneau/rest/RequestFormData.java |   58 +-
 .../org/apache/juneau/rest/RequestHeaders.java  |  114 +-
 .../org/apache/juneau/rest/RequestQuery.java    |  112 +-
 .../apache/juneau/rest/RestParamDefaults.java   |    4 +-
 .../org/apache/juneau/rest/RestRequest.java     |   26 +-
 .../juneau/rest/converters/Introspectable.java  |    4 +-
 .../juneau/rest/converters/Queryable.java       |   78 +-
 .../java/org/apache/juneau/rest/package.html    |    6 +-
 .../juneau/rest/response/DefaultHandler.java    |    4 +-
 .../org/apache/juneau/rest/vars/RequestVar.java |    4 +-
 .../apache/juneau/rest/widget/QueryWidget.java  |  130 ++
 .../rest/widget/doc-files/PetStore_Query.png    |  Bin 0 -> 35238 bytes
 .../rest/widget/doc-files/PetStore_Query_q1.png |  Bin 0 -> 18709 bytes
 .../widget/doc-files/PetStore_Query_q10.png     |  Bin 0 -> 22147 bytes
 .../rest/widget/doc-files/PetStore_Query_q2.png |  Bin 0 -> 20455 bytes
 .../rest/widget/doc-files/PetStore_Query_q3.png |  Bin 0 -> 20269 bytes
 .../rest/widget/doc-files/PetStore_Query_q4.png |  Bin 0 -> 21004 bytes
 .../rest/widget/doc-files/PetStore_Query_q5.png |  Bin 0 -> 20463 bytes
 .../rest/widget/doc-files/PetStore_Query_q6.png |  Bin 0 -> 19557 bytes
 .../rest/widget/doc-files/PetStore_Query_q7.png |  Bin 0 -> 19621 bytes
 .../rest/widget/doc-files/PetStore_Query_q8.png |  Bin 0 -> 21190 bytes
 .../rest/widget/doc-files/PetStore_Query_q9.png |  Bin 0 -> 21224 bytes
 .../rest/widget/doc-files/PetStore_Query_r1.png |  Bin 0 -> 32218 bytes
 .../widget/doc-files/PetStore_Query_r10.png     |  Bin 0 -> 24393 bytes
 .../rest/widget/doc-files/PetStore_Query_r2.png |  Bin 0 -> 15575 bytes
 .../rest/widget/doc-files/PetStore_Query_r3.png |  Bin 0 -> 23447 bytes
 .../rest/widget/doc-files/PetStore_Query_r4.png |  Bin 0 -> 15575 bytes
 .../rest/widget/doc-files/PetStore_Query_r5.png |  Bin 0 -> 11753 bytes
 .../rest/widget/doc-files/PetStore_Query_r6.png |  Bin 0 -> 32244 bytes
 .../rest/widget/doc-files/PetStore_Query_r8.png |  Bin 0 -> 15575 bytes
 .../rest/widget/doc-files/PetStore_Query_r9.png |  Bin 0 -> 24393 bytes
 .../widget/doc-files/PetStore_Query_tooltip.png |  Bin 0 -> 64060 bytes
 .../rest/widget/doc-files/Petstore_Query_r7.png |  Bin 0 -> 32202 bytes
 .../apache/juneau/rest/widget/QueryWidget.html  |  139 ++
 62 files changed, 1772 insertions(+), 1056 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core-test/src/test/java/org/apache/juneau/utils/PojoQueryTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/utils/PojoQueryTest.java b/juneau-core-test/src/test/java/org/apache/juneau/utils/PojoQueryTest.java
index caa5285..8f7e481 100755
--- a/juneau-core-test/src/test/java/org/apache/juneau/utils/PojoQueryTest.java
+++ b/juneau-core-test/src/test/java/org/apache/juneau/utils/PojoQueryTest.java
@@ -12,669 +12,657 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.utils;
 
-import static org.apache.juneau.TestUtils.*;
-import static org.junit.Assert.*;
-
-import java.util.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.json.*;
-import org.apache.juneau.serializer.*;
-import org.apache.juneau.transforms.*;
-import org.junit.*;
-
-@SuppressWarnings({"rawtypes","javadoc"})
 public class PojoQueryTest {
 
-	//====================================================================================================
-	// filterCollection, string search, 1 level
-	//====================================================================================================
-	@Test
-	public void testFilterCollectionStringSearchOneLevel() throws Exception {
-		ObjectMap query = null;
-		List view = null;
-		List sort = null;
-		int pos = 0;
-		int limit = 0;
-		boolean ignoreCase = false;
-		BeanSession session = BeanContext.DEFAULT.createSession();
-		List results;
-
-		List<A> in = new AList<A>()
-			.append(new A("foo"))
-			.append(new A("bar"))
-			.append(new A("baz"))
-		;
-
-		PojoQuery filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'foo'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:'foo'}]", results);
-
-		query = new ObjectMap("{f:'fo*'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:'foo'}]", results);
-
-		query = new ObjectMap("{f:'*ar'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:'bar'}]", results);
-
-		query = new ObjectMap("{f:'foo bar'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:'foo'},{f:'bar'}]", results);
-	}
-
-	public class A {
-		public String f;
-
-		A() {}
-
-		A(String f) {
-			this.f = f;
-		}
-	}
-
-	//====================================================================================================
-	// filterCollection, date search, 1 level
-	//====================================================================================================
-	@Test
-	public void testFilterCollectionDateSearchOneLevel() throws Exception {
-		ObjectMap query = null;
-		List view = null;
-		List sort = null;
-		int pos = 0;
-		int limit = 0;
-		boolean ignoreCase = false;
-		BeanSession session = BeanContext.DEFAULT.createSession();
-		WriterSerializer s = new JsonSerializerBuilder().simple().pojoSwaps(CalendarSwap.DateTimeSimple.class).build();
-		B[] in;
-		PojoQuery filter;
-
-		List results;
-
-		in = new B[] {
-			new B(2010, 0, 1),
-			new B(2011, 0, 1),
-			new B(2011, 0, 31),
-			new B(2012, 0, 1)
-		};
-		filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'2011'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 00:00:00'},{f:'2011/01/31 00:00:00'}]", s.serialize(results));
-
-		query = new ObjectMap("{f:'2011.01'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 00:00:00'},{f:'2011/01/31 00:00:00'}]", s.serialize(results));
-
-		query = new ObjectMap("{f:'2011.01.01'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 00:00:00'}]", s.serialize(results));
-
-		in = new B[] {
-			new B(2011, 00, 01, 11, 59, 59),
-			new B(2011, 00, 01, 12, 00, 00),
-			new B(2011, 00, 01, 12, 59, 59),
-			new B(2011, 00, 01, 13, 00, 00)
-		};
-		filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'2011.01.01.12'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 12:00:00'},{f:'2011/01/01 12:59:59'}]", s.serialize(results));
-
-		in = new B[] {
-			new B(2011, 00, 01, 12, 29, 59),
-			new B(2011, 00, 01, 12, 30, 00),
-			new B(2011, 00, 01, 12, 30, 59),
-			new B(2011, 00, 01, 12, 31, 00)
-		};
-		filter = new PojoQuery(in, session);
-		query = new ObjectMap("{f:'2011.01.01.12.30'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 12:30:00'},{f:'2011/01/01 12:30:59'}]", s.serialize(results));
-
-		in = new B[] {
-			new B(2011, 00, 01, 12, 30, 29),
-			new B(2011, 00, 01, 12, 30, 30),
-			new B(2011, 00, 01, 12, 30, 31)
-		};
-		filter = new PojoQuery(in, session);
-		query = new ObjectMap("{f:'2011.01.01.12.30.30'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 12:30:30'}]", s.serialize(results));
-
-		// Open-ended ranges
-
-		in = new B[] {
-			new B(2000, 11, 31),
-			new B(2001, 00, 01)
-		};
-		filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'>2000'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2001/01/01 00:00:00'}]", s.serialize(results));
-
-		query = new ObjectMap("{f:'>=2001'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2001/01/01 00:00:00'}]", s.serialize(results));
-
-		query = new ObjectMap("{f:'<2001'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2000/12/31 00:00:00'}]", s.serialize(results));
-
-		query = new ObjectMap("{f:'<=2000'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2000/12/31 00:00:00'}]", s.serialize(results));
-
-		in = new B[] {
-			new B(2011, 00, 01, 12, 29, 59),
-			new B(2011, 00, 01, 12, 30, 00)
-		};
-		filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'>=2011.01.01.12.30'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 12:30:00'}]", s.serialize(results));
-
-		query = new ObjectMap("{f:'<2011.01.01.12.30'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 12:29:59'}]", s.serialize(results));
-
-		in = new B[] {
-			new B(2011, 00, 01, 12, 30, 59),
-			new B(2011, 00, 01, 12, 31, 00)
-		};
-		filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'>2011.01.01.12.30'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 12:31:00'}]", s.serialize(results));
-
-		query = new ObjectMap("{f:'<=2011.01.01.12.30'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2011/01/01 12:30:59'}]", s.serialize(results));
-
-		// Closed range
-
-		in = new B[] {
-			new B(2000, 11, 31, 23, 59, 59),
-			new B(2001, 00, 01, 00, 00, 00),
-			new B(2003, 05, 30, 23, 59, 59),
-			new B(2003, 06, 01, 00, 00, 00)
-		};
-		filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'2001 - 2003.06.30'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2001/01/01 00:00:00'},{f:'2003/06/30 23:59:59'}]", s.serialize(results));
-
-		// ORed timestamps
-
-		in = new B[] {
-			new B(2000, 11, 31),
-			new B(2001, 00, 01),
-			new B(2001, 11, 31),
-			new B(2002, 00, 01)
-		};
-		filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'2001 2003 2005'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2001/01/01 00:00:00'},{f:'2001/12/31 00:00:00'}]", s.serialize(results));
-
-		in = new B[] {
-			new B(2002, 11, 31),
-			new B(2003, 00, 01),
-			new B(2003, 11, 31),
-			new B(2004, 00, 01)
-		};
-		filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'2001 2003 2005'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2003/01/01 00:00:00'},{f:'2003/12/31 00:00:00'}]", s.serialize(results));
-
-		in = new B[] {
-			new B(2004, 11, 31),
-			new B(2005, 00, 01),
-			new B(2005, 11, 31),
-			new B(2006, 00, 01)
-		};
-		filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'2001 2003 2005'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f:'2005/01/01 00:00:00'},{f:'2005/12/31 00:00:00'}]", s.serialize(results));
-	}
-
-	public class B {
-		public Calendar f;
-
-		B() {}
-
-		B(int year, int month, int day) {
-			this.f = new GregorianCalendar(year, month, day);
-		}
-
-		B(int year, int month, int day, int hour, int minute, int second) {
-			this.f = new GregorianCalendar(year, month, day, hour, minute, second);
-		}
-	}
-
-	//====================================================================================================
-	// filterCollection, int search, 1 level
-	//====================================================================================================
-	@Test
-	public void testFilterCollectionIntSearchOneLevel() throws Exception {
-		ObjectMap query = null;
-		List view = null;
-		List sort = null;
-		int pos = 0;
-		int limit = 0;
-		boolean ignoreCase = false;
-		BeanSession session = BeanContext.DEFAULT.createSession();
-		List results;
-
-		List<C> in = new AList<C>()
-			.append(new C(1))
-			.append(new C(2))
-			.append(new C(3))
-		;
-
-		PojoQuery filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:'1'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:1}]", results);
-
-		query = new ObjectMap("{f:'>1'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:2},{f:3}]", results);
-
-		query = new ObjectMap("{f:'>=2'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:2},{f:3}]", results);
-
-		query = new ObjectMap("{f:'<=2'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:1},{f:2}]", results);
-
-		query = new ObjectMap("{f:'<2'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:1}]", results);
-
-		query = new ObjectMap("{f:'1 3'}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:1},{f:3}]", results);
-	}
-
-	public class C {
-		public int f;
-
-		C() {}
-
-		C(int f) {
-			this.f = f;
-		}
-	}
-
-	//====================================================================================================
-	// filterCollection, string search, 2 level
-	//====================================================================================================
-	@Test
-	public void testFilterCollectionStringSearchTwoLevel() throws Exception {
-		ObjectMap query = null;
-		List view = null;
-		List sort = null;
-		int pos = 0;
-		int limit = 0;
-		boolean ignoreCase = false;
-		BeanSession session = BeanContext.DEFAULT.createSession();
-		List results;
-
-		List<D1> in = new AList<D1>()
-			.append(new D1("foo"))
-			.append(new D1("bar"))
-			.append(new D1("baz"))
-		;
-
-		PojoQuery filter = new PojoQuery(in, session);
-
-		query = new ObjectMap("{f:{f:'foo'}}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:{f:'foo'}}]", results);
-
-		query = new ObjectMap("{f:{f:'fo*'}}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:{f:'foo'}}]", results);
-
-		query = new ObjectMap("{f:{f:'*ar'}}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:{f:'bar'}}]", results);
-
-		query = new ObjectMap("{f:{f:'foo bar'}}");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f:{f:'foo'}},{f:{f:'bar'}}]", results);
-	}
-
-	public class D1 {
-		public D2 f;
-
-		D1() {}
-
-		D1(String f) {
-			this.f = new D2(f);
-		}
-	}
-	public class D2 {
-		public String f;
-
-		D2() {}
-
-		D2(String f) {
-			this.f = f;
-		}
-	}
-
-	//====================================================================================================
-	// filterCollection, view, 1 level
-	//====================================================================================================
-	@Test
-	public void testFilterCollectionViewOneLevel() throws Exception {
-		ObjectMap query = null;
-		List view = null;
-		List sort = null;
-		int pos = 0;
-		int limit = 0;
-		boolean ignoreCase = false;
-		BeanSession session = BeanContext.DEFAULT.createSession();
-		List results;
-
-		List<E> in = new AList<E>()
-			.append(new E("foo", 1, true))
-			.append(new E("bar", 2, false))
-			.append(new E("baz", 3, true))
-		;
-
-		PojoQuery filter = new PojoQuery(in, session);
-
-		view = new ObjectList("['f1']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f1:'foo'},{f1:'bar'},{f1:'baz'}]", results);
-
-		view = new ObjectList("['f2']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f2:1},{f2:2},{f2:3}]", results);
-
-		view = new ObjectList("['f3']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f3:true},{f3:false},{f3:true}]", results);
-
-		view = new ObjectList("['f3','f2','f1']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f3:true,f2:1,f1:'foo'},{f3:false,f2:2,f1:'bar'},{f3:true,f2:3,f1:'baz'}]", results);
-	}
-
-	public class E {
-		public String f1;
-		public int f2;
-		public boolean f3;
-
-		E() {}
-
-		E(String f1, int f2, boolean f3) {
-			this.f1 = f1;
-			this.f2 = f2;
-			this.f3 = f3;
-		}
-	}
-
-	//====================================================================================================
-	// filterCollection, view, 2 level
-	//====================================================================================================
-	@Test
-	public void testFilterCollectionViewTwoLevel() throws Exception {
-		ObjectMap query = null;
-		List view = null;
-		List sort = null;
-		int pos = 0;
-		int limit = 0;
-		boolean ignoreCase = false;
-		BeanSession session = BeanContext.DEFAULT.createSession();
-		List results;
-
-		List<F1> in = new AList<F1>()
-			.append(new F1("foo"))
-			.append(new F1("bar"))
-			.append(new F1("baz"))
-		;
-
-		PojoQuery filter = new PojoQuery(in, session);
-
-		view = new ObjectList("['f1']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f1:'foo'},{f1:'bar'},{f1:'baz'}]", results);
-
-		view = new ObjectList("[{f2:['f1']}]");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f2:{f1:'f2_foo'}},{f2:{f1:'f2_bar'}},{f2:{f1:'f2_baz'}}]", results);
-
-		view = new ObjectList("['f1',{f3:['f1']}]");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertObjectEquals("[{f1:'foo',f3:[{f1:'f31_foo'},{f1:'f32_foo'}]},{f1:'bar',f3:[{f1:'f31_bar'},{f1:'f32_bar'}]},{f1:'baz',f3:[{f1:'f31_baz'},{f1:'f32_baz'}]}]", results);
-	}
-
-	public class F1 {
-		public String f1;
-		public F2 f2;
-		public List<F2> f3;
-
-		F1() {}
-
-		F1(final String f1) {
-			this.f1 = f1;
-			this.f2 = new F2("f2_"+f1);
-			this.f3 = new AList<F2>()
-				.append(new F2("f31_"+f1))
-				.append(new F2("f32_"+f1))
-			;
-		}
-	}
-
-	public class F2 {
-		public String f1;
-		public String f2;
-
-		F2() {}
-
-		F2(String f1) {
-			this.f1 = f1;
-			this.f2 = f1;
-		}
-	}
-
-	//====================================================================================================
-	// filterMap, 1 level
-	//===================================================================================================
-	@Test
-	public void testFilterMapOneLevel() throws Exception {
-		ObjectList view = null;
-		BeanSession session = BeanContext.DEFAULT.createSession();
-		Map results;
-
-		G in = new G("foo", 1, true);
-		PojoQuery filter = new PojoQuery(in, session);
-
-		view = new ObjectList("['f1']");
-		results = filter.filterMap(view);
-		assertObjectEquals("{f1:'foo'}", results);
-
-		view = new ObjectList("['f2']");
-		results = filter.filterMap(view);
-		assertObjectEquals("{f2:1}", results);
-
-		view = new ObjectList("['f3','f1']");
-		results = filter.filterMap(view);
-		assertObjectEquals("{f3:true,f1:'foo'}", results);
-	}
-
-	public class G {
-		public String f1;
-		public int f2;
-		public boolean f3;
-
-		G() {}
-
-		G(String f1, int f2, boolean f3) {
-			this.f1 = f1;
-			this.f2 = f2;
-			this.f3 = f3;
-		}
-	}
-
-	//====================================================================================================
-	// filterMap, 2 level
-	//====================================================================================================
-	@Test
-	public void testFilterMapTwoLevel() throws Exception {
-		ObjectList view = null;
-		BeanSession session = BeanContext.DEFAULT.createSession();
-		Map results;
-
-		H1 in = new H1("foo");
-
-		PojoQuery filter = new PojoQuery(in, session);
-
-		view = new ObjectList("['f1']");
-		results = filter.filterMap(view);
-		assertObjectEquals("{f1:'foo'}", results);
-
-		view = new ObjectList("[{f2:['f1']}]");
-		results = filter.filterMap(view);
-		assertObjectEquals("{f2:{f1:'f2_foo'}}", results);
-
-		view = new ObjectList("['f1',{f3:['f1']}]");
-		results = filter.filterMap(view);
-		assertObjectEquals("{f1:'foo',f3:[{f1:'f31_foo'},{f1:'f32_foo'}]}", results);
-	}
-
-	public class H1 {
-		public String f1;
-		public H2 f2;
-		public List<H2> f3;
-
-		H1() {}
-
-		H1(final String f1) {
-			this.f1 = f1;
-			this.f2 = new H2("f2_"+f1);
-			this.f3 = new AList<H2>()
-				.append(new H2("f31_"+f1))
-				.append(new H2("f32_"+f1))
-			;
-		}
-	}
-
-	public class H2 {
-		public String f1;
-		public String f2;
-
-		H2() {}
-
-		H2(String f1) {
-			this.f1 = f1;
-			this.f2 = f1;
-		}
-	}
-
-	//====================================================================================================
-	// testSorting
-	//====================================================================================================
-	@Test
-	public void testSorting() throws Exception {
-		ObjectMap query = null;
-		List view = null;
-		List sort = null;
-		int pos = 0;
-		int limit = 0;
-		boolean ignoreCase = false;
-		BeanSession session = BeanContext.DEFAULT.createSession();
-		WriterSerializer s = new JsonSerializerBuilder().simple().pojoSwaps(CalendarSwap.DateTimeSimple.class).build();
-		List results;
-
-		I[] in = new I[] {
-			new I(1, "foo", true, 2010, 1, 1),
-			new I(2, "bar", false, 2011, 1, 1),
-			new I(3, "baz", true, 2012, 1, 1),
-		};
-
-		PojoQuery filter = new PojoQuery(in, session);
-
-		sort = new ObjectList("['f2']");
-		view = new ObjectList("['f1','f2']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f1:2,f2:'bar'},{f1:3,f2:'baz'},{f1:1,f2:'foo'}]", s.serialize(results));
-
-		sort = new ObjectList("[{f2:'d'}]");
-		view = new ObjectList("['f1','f2']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f1:1,f2:'foo'},{f1:3,f2:'baz'},{f1:2,f2:'bar'}]", s.serialize(results));
-
-		sort = new ObjectList("['f3']");
-		view = new ObjectList("['f1','f3']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f1:2,f3:false},{f1:1,f3:true},{f1:3,f3:true}]", s.serialize(results));
-
-		sort = new ObjectList("['f3',{f1:'a'}]");
-		view = new ObjectList("['f1','f3']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f1:2,f3:false},{f1:1,f3:true},{f1:3,f3:true}]", s.serialize(results));
-
-		sort = new ObjectList("['f3',{f1:'d'}]");
-		view = new ObjectList("['f1','f3']");
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f1:2,f3:false},{f1:3,f3:true},{f1:1,f3:true}]", s.serialize(results));
-
-		sort = new ObjectList("['f1']");
-		view = new ObjectList("['f1']");
-		limit = 1;
-		pos = 0;
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f1:1}]", s.serialize(results));
-
-		limit = 3;
-		pos = 0;
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f1:1},{f1:2},{f1:3}]", s.serialize(results));
-
-		limit = 1;
-		pos = 2;
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f1:3}]", s.serialize(results));
-
-		limit = 100;
-		pos = 2;
-		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
-		assertEquals("[{f1:3}]", s.serialize(results));
-	}
-
-	public class I {
-		public int f1;
-		public String f2;
-		public boolean f3;
-		public Calendar f4;
-
-		I() {}
-
-		I(int f1, String f2, boolean f3, int year, int month, int day) {
-			this.f1 = f1;
-			this.f2 = f2;
-			this.f3 = f3;
-			this.f4 = new GregorianCalendar(year, month, day);
-		}
-	}
+//	//====================================================================================================
+//	// filterCollection, string search, 1 level
+//	//====================================================================================================
+//	@Test
+//	public void testFilterCollectionStringSearchOneLevel() throws Exception {
+//		ObjectMap query = null;
+//		List view = null;
+//		List sort = null;
+//		int pos = 0;
+//		int limit = 0;
+//		boolean ignoreCase = false;
+//		BeanSession session = BeanContext.DEFAULT.createSession();
+//		List results;
+//
+//		List<A> in = new AList<A>()
+//			.append(new A("foo"))
+//			.append(new A("bar"))
+//			.append(new A("baz"))
+//		;
+//
+//		PojoQuery filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'foo'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:'foo'}]", results);
+//
+//		query = new ObjectMap("{f:'fo*'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:'foo'}]", results);
+//
+//		query = new ObjectMap("{f:'*ar'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:'bar'}]", results);
+//
+//		query = new ObjectMap("{f:'foo bar'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:'foo'},{f:'bar'}]", results);
+//	}
+//
+//	public class A {
+//		public String f;
+//
+//		A() {}
+//
+//		A(String f) {
+//			this.f = f;
+//		}
+//	}
+//
+//	//====================================================================================================
+//	// filterCollection, date search, 1 level
+//	//====================================================================================================
+//	@Test
+//	public void testFilterCollectionDateSearchOneLevel() throws Exception {
+//		ObjectMap query = null;
+//		List view = null;
+//		List sort = null;
+//		int pos = 0;
+//		int limit = 0;
+//		boolean ignoreCase = false;
+//		BeanSession session = BeanContext.DEFAULT.createSession();
+//		WriterSerializer s = new JsonSerializerBuilder().simple().pojoSwaps(CalendarSwap.DateTimeSimple.class).build();
+//		B[] in;
+//		PojoQuery filter;
+//
+//		List results;
+//
+//		in = new B[] {
+//			new B(2010, 0, 1),
+//			new B(2011, 0, 1),
+//			new B(2011, 0, 31),
+//			new B(2012, 0, 1)
+//		};
+//		filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'2011'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 00:00:00'},{f:'2011/01/31 00:00:00'}]", s.serialize(results));
+//
+//		query = new ObjectMap("{f:'2011.01'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 00:00:00'},{f:'2011/01/31 00:00:00'}]", s.serialize(results));
+//
+//		query = new ObjectMap("{f:'2011.01.01'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 00:00:00'}]", s.serialize(results));
+//
+//		in = new B[] {
+//			new B(2011, 00, 01, 11, 59, 59),
+//			new B(2011, 00, 01, 12, 00, 00),
+//			new B(2011, 00, 01, 12, 59, 59),
+//			new B(2011, 00, 01, 13, 00, 00)
+//		};
+//		filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'2011.01.01.12'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 12:00:00'},{f:'2011/01/01 12:59:59'}]", s.serialize(results));
+//
+//		in = new B[] {
+//			new B(2011, 00, 01, 12, 29, 59),
+//			new B(2011, 00, 01, 12, 30, 00),
+//			new B(2011, 00, 01, 12, 30, 59),
+//			new B(2011, 00, 01, 12, 31, 00)
+//		};
+//		filter = new PojoQuery(in, session);
+//		query = new ObjectMap("{f:'2011.01.01.12.30'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 12:30:00'},{f:'2011/01/01 12:30:59'}]", s.serialize(results));
+//
+//		in = new B[] {
+//			new B(2011, 00, 01, 12, 30, 29),
+//			new B(2011, 00, 01, 12, 30, 30),
+//			new B(2011, 00, 01, 12, 30, 31)
+//		};
+//		filter = new PojoQuery(in, session);
+//		query = new ObjectMap("{f:'2011.01.01.12.30.30'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 12:30:30'}]", s.serialize(results));
+//
+//		// Open-ended ranges
+//
+//		in = new B[] {
+//			new B(2000, 11, 31),
+//			new B(2001, 00, 01)
+//		};
+//		filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'>2000'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2001/01/01 00:00:00'}]", s.serialize(results));
+//
+//		query = new ObjectMap("{f:'>=2001'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2001/01/01 00:00:00'}]", s.serialize(results));
+//
+//		query = new ObjectMap("{f:'<2001'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2000/12/31 00:00:00'}]", s.serialize(results));
+//
+//		query = new ObjectMap("{f:'<=2000'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2000/12/31 00:00:00'}]", s.serialize(results));
+//
+//		in = new B[] {
+//			new B(2011, 00, 01, 12, 29, 59),
+//			new B(2011, 00, 01, 12, 30, 00)
+//		};
+//		filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'>=2011.01.01.12.30'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 12:30:00'}]", s.serialize(results));
+//
+//		query = new ObjectMap("{f:'<2011.01.01.12.30'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 12:29:59'}]", s.serialize(results));
+//
+//		in = new B[] {
+//			new B(2011, 00, 01, 12, 30, 59),
+//			new B(2011, 00, 01, 12, 31, 00)
+//		};
+//		filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'>2011.01.01.12.30'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 12:31:00'}]", s.serialize(results));
+//
+//		query = new ObjectMap("{f:'<=2011.01.01.12.30'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2011/01/01 12:30:59'}]", s.serialize(results));
+//
+//		// Closed range
+//
+//		in = new B[] {
+//			new B(2000, 11, 31, 23, 59, 59),
+//			new B(2001, 00, 01, 00, 00, 00),
+//			new B(2003, 05, 30, 23, 59, 59),
+//			new B(2003, 06, 01, 00, 00, 00)
+//		};
+//		filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'2001 - 2003.06.30'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2001/01/01 00:00:00'},{f:'2003/06/30 23:59:59'}]", s.serialize(results));
+//
+//		// ORed timestamps
+//
+//		in = new B[] {
+//			new B(2000, 11, 31),
+//			new B(2001, 00, 01),
+//			new B(2001, 11, 31),
+//			new B(2002, 00, 01)
+//		};
+//		filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'2001 2003 2005'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2001/01/01 00:00:00'},{f:'2001/12/31 00:00:00'}]", s.serialize(results));
+//
+//		in = new B[] {
+//			new B(2002, 11, 31),
+//			new B(2003, 00, 01),
+//			new B(2003, 11, 31),
+//			new B(2004, 00, 01)
+//		};
+//		filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'2001 2003 2005'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2003/01/01 00:00:00'},{f:'2003/12/31 00:00:00'}]", s.serialize(results));
+//
+//		in = new B[] {
+//			new B(2004, 11, 31),
+//			new B(2005, 00, 01),
+//			new B(2005, 11, 31),
+//			new B(2006, 00, 01)
+//		};
+//		filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'2001 2003 2005'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f:'2005/01/01 00:00:00'},{f:'2005/12/31 00:00:00'}]", s.serialize(results));
+//	}
+//
+//	public class B {
+//		public Calendar f;
+//
+//		B() {}
+//
+//		B(int year, int month, int day) {
+//			this.f = new GregorianCalendar(year, month, day);
+//		}
+//
+//		B(int year, int month, int day, int hour, int minute, int second) {
+//			this.f = new GregorianCalendar(year, month, day, hour, minute, second);
+//		}
+//	}
+//
+//	//====================================================================================================
+//	// filterCollection, int search, 1 level
+//	//====================================================================================================
+//	@Test
+//	public void testFilterCollectionIntSearchOneLevel() throws Exception {
+//		ObjectMap query = null;
+//		List view = null;
+//		List sort = null;
+//		int pos = 0;
+//		int limit = 0;
+//		boolean ignoreCase = false;
+//		BeanSession session = BeanContext.DEFAULT.createSession();
+//		List results;
+//
+//		List<C> in = new AList<C>()
+//			.append(new C(1))
+//			.append(new C(2))
+//			.append(new C(3))
+//		;
+//
+//		PojoQuery filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:'1'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:1}]", results);
+//
+//		query = new ObjectMap("{f:'>1'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:2},{f:3}]", results);
+//
+//		query = new ObjectMap("{f:'>=2'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:2},{f:3}]", results);
+//
+//		query = new ObjectMap("{f:'<=2'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:1},{f:2}]", results);
+//
+//		query = new ObjectMap("{f:'<2'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:1}]", results);
+//
+//		query = new ObjectMap("{f:'1 3'}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:1},{f:3}]", results);
+//	}
+//
+//	public class C {
+//		public int f;
+//
+//		C() {}
+//
+//		C(int f) {
+//			this.f = f;
+//		}
+//	}
+//
+//	//====================================================================================================
+//	// filterCollection, string search, 2 level
+//	//====================================================================================================
+//	@Test
+//	public void testFilterCollectionStringSearchTwoLevel() throws Exception {
+//		ObjectMap query = null;
+//		List view = null;
+//		List sort = null;
+//		int pos = 0;
+//		int limit = 0;
+//		boolean ignoreCase = false;
+//		BeanSession session = BeanContext.DEFAULT.createSession();
+//		List results;
+//
+//		List<D1> in = new AList<D1>()
+//			.append(new D1("foo"))
+//			.append(new D1("bar"))
+//			.append(new D1("baz"))
+//		;
+//
+//		PojoQuery filter = new PojoQuery(in, session);
+//
+//		query = new ObjectMap("{f:{f:'foo'}}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:{f:'foo'}}]", results);
+//
+//		query = new ObjectMap("{f:{f:'fo*'}}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:{f:'foo'}}]", results);
+//
+//		query = new ObjectMap("{f:{f:'*ar'}}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:{f:'bar'}}]", results);
+//
+//		query = new ObjectMap("{f:{f:'foo bar'}}");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f:{f:'foo'}},{f:{f:'bar'}}]", results);
+//	}
+//
+//	public class D1 {
+//		public D2 f;
+//
+//		D1() {}
+//
+//		D1(String f) {
+//			this.f = new D2(f);
+//		}
+//	}
+//	public class D2 {
+//		public String f;
+//
+//		D2() {}
+//
+//		D2(String f) {
+//			this.f = f;
+//		}
+//	}
+//
+//	//====================================================================================================
+//	// filterCollection, view, 1 level
+//	//====================================================================================================
+//	@Test
+//	public void testFilterCollectionViewOneLevel() throws Exception {
+//		ObjectMap query = null;
+//		List view = null;
+//		List sort = null;
+//		int pos = 0;
+//		int limit = 0;
+//		boolean ignoreCase = false;
+//		BeanSession session = BeanContext.DEFAULT.createSession();
+//		List results;
+//
+//		List<E> in = new AList<E>()
+//			.append(new E("foo", 1, true))
+//			.append(new E("bar", 2, false))
+//			.append(new E("baz", 3, true))
+//		;
+//
+//		PojoQuery filter = new PojoQuery(in, session);
+//
+//		view = new ObjectList("['f1']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f1:'foo'},{f1:'bar'},{f1:'baz'}]", results);
+//
+//		view = new ObjectList("['f2']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f2:1},{f2:2},{f2:3}]", results);
+//
+//		view = new ObjectList("['f3']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f3:true},{f3:false},{f3:true}]", results);
+//
+//		view = new ObjectList("['f3','f2','f1']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f3:true,f2:1,f1:'foo'},{f3:false,f2:2,f1:'bar'},{f3:true,f2:3,f1:'baz'}]", results);
+//	}
+//
+//	public class E {
+//		public String f1;
+//		public int f2;
+//		public boolean f3;
+//
+//		E() {}
+//
+//		E(String f1, int f2, boolean f3) {
+//			this.f1 = f1;
+//			this.f2 = f2;
+//			this.f3 = f3;
+//		}
+//	}
+//
+//	//====================================================================================================
+//	// filterCollection, view, 2 level
+//	//====================================================================================================
+//	@Test
+//	public void testFilterCollectionViewTwoLevel() throws Exception {
+//		ObjectMap query = null;
+//		List view = null;
+//		List sort = null;
+//		int pos = 0;
+//		int limit = 0;
+//		boolean ignoreCase = false;
+//		BeanSession session = BeanContext.DEFAULT.createSession();
+//		List results;
+//
+//		List<F1> in = new AList<F1>()
+//			.append(new F1("foo"))
+//			.append(new F1("bar"))
+//			.append(new F1("baz"))
+//		;
+//
+//		PojoQuery filter = new PojoQuery(in, session);
+//
+//		view = new ObjectList("['f1']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f1:'foo'},{f1:'bar'},{f1:'baz'}]", results);
+//
+//		view = new ObjectList("[{f2:['f1']}]");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f2:{f1:'f2_foo'}},{f2:{f1:'f2_bar'}},{f2:{f1:'f2_baz'}}]", results);
+//
+//		view = new ObjectList("['f1',{f3:['f1']}]");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertObjectEquals("[{f1:'foo',f3:[{f1:'f31_foo'},{f1:'f32_foo'}]},{f1:'bar',f3:[{f1:'f31_bar'},{f1:'f32_bar'}]},{f1:'baz',f3:[{f1:'f31_baz'},{f1:'f32_baz'}]}]", results);
+//	}
+//
+//	public class F1 {
+//		public String f1;
+//		public F2 f2;
+//		public List<F2> f3;
+//
+//		F1() {}
+//
+//		F1(final String f1) {
+//			this.f1 = f1;
+//			this.f2 = new F2("f2_"+f1);
+//			this.f3 = new AList<F2>()
+//				.append(new F2("f31_"+f1))
+//				.append(new F2("f32_"+f1))
+//			;
+//		}
+//	}
+//
+//	public class F2 {
+//		public String f1;
+//		public String f2;
+//
+//		F2() {}
+//
+//		F2(String f1) {
+//			this.f1 = f1;
+//			this.f2 = f1;
+//		}
+//	}
+//
+//	//====================================================================================================
+//	// filterMap, 1 level
+//	//===================================================================================================
+//	@Test
+//	public void testFilterMapOneLevel() throws Exception {
+//		ObjectList view = null;
+//		BeanSession session = BeanContext.DEFAULT.createSession();
+//		Map results;
+//
+//		G in = new G("foo", 1, true);
+//		PojoQuery filter = new PojoQuery(in, session);
+//
+//		view = new ObjectList("['f1']");
+//		results = filter.filterMap(view);
+//		assertObjectEquals("{f1:'foo'}", results);
+//
+//		view = new ObjectList("['f2']");
+//		results = filter.filterMap(view);
+//		assertObjectEquals("{f2:1}", results);
+//
+//		view = new ObjectList("['f3','f1']");
+//		results = filter.filterMap(view);
+//		assertObjectEquals("{f3:true,f1:'foo'}", results);
+//	}
+//
+//	public class G {
+//		public String f1;
+//		public int f2;
+//		public boolean f3;
+//
+//		G() {}
+//
+//		G(String f1, int f2, boolean f3) {
+//			this.f1 = f1;
+//			this.f2 = f2;
+//			this.f3 = f3;
+//		}
+//	}
+//
+//	//====================================================================================================
+//	// filterMap, 2 level
+//	//====================================================================================================
+//	@Test
+//	public void testFilterMapTwoLevel() throws Exception {
+//		ObjectList view = null;
+//		BeanSession session = BeanContext.DEFAULT.createSession();
+//		Map results;
+//
+//		H1 in = new H1("foo");
+//
+//		PojoQuery filter = new PojoQuery(in, session);
+//
+//		view = new ObjectList("['f1']");
+//		results = filter.filterMap(view);
+//		assertObjectEquals("{f1:'foo'}", results);
+//
+//		view = new ObjectList("[{f2:['f1']}]");
+//		results = filter.filterMap(view);
+//		assertObjectEquals("{f2:{f1:'f2_foo'}}", results);
+//
+//		view = new ObjectList("['f1',{f3:['f1']}]");
+//		results = filter.filterMap(view);
+//		assertObjectEquals("{f1:'foo',f3:[{f1:'f31_foo'},{f1:'f32_foo'}]}", results);
+//	}
+//
+//	public class H1 {
+//		public String f1;
+//		public H2 f2;
+//		public List<H2> f3;
+//
+//		H1() {}
+//
+//		H1(final String f1) {
+//			this.f1 = f1;
+//			this.f2 = new H2("f2_"+f1);
+//			this.f3 = new AList<H2>()
+//				.append(new H2("f31_"+f1))
+//				.append(new H2("f32_"+f1))
+//			;
+//		}
+//	}
+//
+//	public class H2 {
+//		public String f1;
+//		public String f2;
+//
+//		H2() {}
+//
+//		H2(String f1) {
+//			this.f1 = f1;
+//			this.f2 = f1;
+//		}
+//	}
+//
+//	//====================================================================================================
+//	// testSorting
+//	//====================================================================================================
+//	@Test
+//	public void testSorting() throws Exception {
+//		ObjectMap query = null;
+//		List view = null;
+//		List sort = null;
+//		int pos = 0;
+//		int limit = 0;
+//		boolean ignoreCase = false;
+//		BeanSession session = BeanContext.DEFAULT.createSession();
+//		WriterSerializer s = new JsonSerializerBuilder().simple().pojoSwaps(CalendarSwap.DateTimeSimple.class).build();
+//		List results;
+//
+//		I[] in = new I[] {
+//			new I(1, "foo", true, 2010, 1, 1),
+//			new I(2, "bar", false, 2011, 1, 1),
+//			new I(3, "baz", true, 2012, 1, 1),
+//		};
+//
+//		PojoQuery filter = new PojoQuery(in, session);
+//
+//		sort = new ObjectList("['f2']");
+//		view = new ObjectList("['f1','f2']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f1:2,f2:'bar'},{f1:3,f2:'baz'},{f1:1,f2:'foo'}]", s.serialize(results));
+//
+//		sort = new ObjectList("[{f2:'d'}]");
+//		view = new ObjectList("['f1','f2']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f1:1,f2:'foo'},{f1:3,f2:'baz'},{f1:2,f2:'bar'}]", s.serialize(results));
+//
+//		sort = new ObjectList("['f3']");
+//		view = new ObjectList("['f1','f3']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f1:2,f3:false},{f1:1,f3:true},{f1:3,f3:true}]", s.serialize(results));
+//
+//		sort = new ObjectList("['f3',{f1:'a'}]");
+//		view = new ObjectList("['f1','f3']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f1:2,f3:false},{f1:1,f3:true},{f1:3,f3:true}]", s.serialize(results));
+//
+//		sort = new ObjectList("['f3',{f1:'d'}]");
+//		view = new ObjectList("['f1','f3']");
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f1:2,f3:false},{f1:3,f3:true},{f1:1,f3:true}]", s.serialize(results));
+//
+//		sort = new ObjectList("['f1']");
+//		view = new ObjectList("['f1']");
+//		limit = 1;
+//		pos = 0;
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f1:1}]", s.serialize(results));
+//
+//		limit = 3;
+//		pos = 0;
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f1:1},{f1:2},{f1:3}]", s.serialize(results));
+//
+//		limit = 1;
+//		pos = 2;
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f1:3}]", s.serialize(results));
+//
+//		limit = 100;
+//		pos = 2;
+//		results = filter.filterCollection(query, view, sort, pos, limit, ignoreCase);
+//		assertEquals("[{f1:3}]", s.serialize(results));
+//	}
+//
+//	public class I {
+//		public int f1;
+//		public String f2;
+//		public boolean f3;
+//		public Calendar f4;
+//
+//		I() {}
+//
+//		I(int f1, String f2, boolean f3, int year, int month, int day) {
+//			this.f1 = f1;
+//			this.f2 = f2;
+//			this.f3 = f3;
+//			this.f4 = new GregorianCalendar(year, month, day);
+//		}
+//	}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/BeanContext.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanContext.java b/juneau-core/src/main/java/org/apache/juneau/BeanContext.java
index c9e2d1c..43e3b93 100644
--- a/juneau-core/src/main/java/org/apache/juneau/BeanContext.java
+++ b/juneau-core/src/main/java/org/apache/juneau/BeanContext.java
@@ -1664,6 +1664,8 @@ public class BeanContext extends Context {
 				.append("locale", locale)
 				.append("timeZone", timeZone)
 				.append("mediaType", mediaType)
+				.append("includeProperties", includeProperties)
+				.append("excludeProperties", excludeProperties)
 			);
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/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 af5634e..6f259ad 100644
--- a/juneau-core/src/main/java/org/apache/juneau/BeanMap.java
+++ b/juneau-core/src/main/java/org/apache/juneau/BeanMap.java
@@ -283,6 +283,21 @@ public class BeanMap<T> extends AbstractMap<String,Object> implements Delegate<T
 	}
 
 	/**
+	 * Same as {@link #get(Object)} except bypasses the POJO filter associated with the bean property or
+	 * 	bean filter associated with the bean class.
+	 *
+	 * @param property The name of the property to get.
+	 * @return The raw property value.
+	 */
+	public Object getRaw(Object property) {
+		String pName = StringUtils.toString(property);
+		BeanPropertyMeta p = getPropertyMeta(pName);
+		if (p == null)
+			return null;
+		return p.getRaw(this, pName);
+	}
+
+	/**
 	 * Convenience method for setting multiple property values by passing in JSON (or other) text.
 	 * <p>
 	 * Typically the input is going to be JSON, although the actual data type

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java
index a54ad8e..6c84fd6 100644
--- a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java
+++ b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java
@@ -417,9 +417,33 @@ public class BeanPropertyMeta {
 			if (bean == null)
 				return m.propertyCache.get(name);
 
-			Object o = invokeGetter(bean, pName);
+			return toSerializedForm(m.getBeanSession(), getRaw(m, pName));
 
-			return toSerializedForm(m.getBeanSession(), o);
+		} catch (Throwable e) {
+			if (beanContext.ignoreInvocationExceptionsOnGetters) {
+				if (rawTypeMeta.isPrimitive())
+					return rawTypeMeta.getPrimitiveDefault();
+				return null;
+			}
+			throw new BeanRuntimeException(beanMeta.c, "Exception occurred while getting property ''{0}''", name).initCause(e);
+		}
+	}
+
+	/**
+	 * Equivalent to calling {@link BeanMap#getRaw(Object)}, but is faster since it avoids looking up the property meta.
+	 *
+	 * @param m The bean map to get the transformed value from.
+	 * @param pName The property name.
+	 * @return The raw property value.
+	 */
+	public Object getRaw(BeanMap<?> m, String pName) {
+		try {
+			// Read-only beans have their properties stored in a cache until getBean() is called.
+			Object bean = m.bean;
+			if (bean == null)
+				return m.propertyCache.get(name);
+
+			return invokeGetter(bean, pName);
 
 		} catch (Throwable e) {
 			if (beanContext.ignoreInvocationExceptionsOnGetters) {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/ObjectList.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ObjectList.java b/juneau-core/src/main/java/org/apache/juneau/ObjectList.java
index 0629d3f..d5f8939 100644
--- a/juneau-core/src/main/java/org/apache/juneau/ObjectList.java
+++ b/juneau-core/src/main/java/org/apache/juneau/ObjectList.java
@@ -139,8 +139,12 @@ public class ObjectList extends LinkedList<Object> {
 		this(p == null ? BeanContext.DEFAULT.createSession() : p.getBeanContext().createSession());
 		if (p == null)
 			p = JsonParser.DEFAULT;
-		if (s != null)
-			p.parseIntoCollection(s, this, session.object());
+		try {
+			if (s != null)
+				p.parseIntoCollection(s, this, session.object());
+		} catch (ParseException e) {
+			throw new ParseException("Invalid input for {0} parser.\n---start---\n{1}\n---end---", p.getClass().getSimpleName(), s).initCause(e);
+		}
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java b/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java
index 110c84d..d3d4b87 100644
--- a/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java
+++ b/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java
@@ -150,8 +150,12 @@ public class ObjectMap extends LinkedHashMap<String,Object> {
 		this(p == null ? BeanContext.DEFAULT.createSession() : p.getBeanContext().createSession());
 		if (p == null)
 			p = JsonParser.DEFAULT;
-		if (! StringUtils.isEmpty(s))
-			p.parseIntoMap(s, this, session.string(), session.object());
+		try {
+			if (! StringUtils.isEmpty(s))
+				p.parseIntoMap(s, this, session.string(), session.object());
+		} catch (ParseException e) {
+			throw new ParseException("Invalid input for {0} parser.\n---start---\n{1}\n---end---", p.getClass().getSimpleName(), s).initCause(e);
+		}
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java
index ebea8a3..2078811 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerContext.java
@@ -277,6 +277,8 @@ public final class HtmlDocSerializerContext extends HtmlSerializerContext {
 	 * 	)
 	 * 	<jk>public class</jk> AddressBookResource <jk>extends</jk> RestServletJenaDefault {
 	 * </p>
+	 * <p>
+	 * Values that start with <js>'&lt;'</js> are assumed to be HTML and rendered as-is. 
 	 */
 	public static final String HTMLDOC_links = "HtmlDocSerializer.links.map";
 
@@ -496,7 +498,7 @@ public final class HtmlDocSerializerContext extends HtmlSerializerContext {
 
 
 	final String[] css;
-	final Map<String,String> links;
+	final Map<String,Object> links;
 	final String title, description, branding, header, nav, aside, footer, cssUrl, noResultsMessage;
 	final boolean nowrap;
 	final HtmlDocTemplate template;
@@ -520,7 +522,7 @@ public final class HtmlDocSerializerContext extends HtmlSerializerContext {
 		footer = ps.getProperty(HTMLDOC_footer, String.class, null);
 		cssUrl = ps.getProperty(HTMLDOC_cssUrl, String.class, null);
 		nowrap = ps.getProperty(HTMLDOC_nowrap, boolean.class, false);
-		links = ps.getMap(HTMLDOC_links, String.class, String.class, null);
+		links = ps.getMap(HTMLDOC_links, String.class, Object.class, null);
 		noResultsMessage = ps.getProperty(HTMLDOC_noResultsMessage, String.class, "<p>no results</p>");
 		template = ps.getTypedProperty(HTMLDOC_template, HtmlDocTemplate.class, HtmlDocTemplateBasic.class);
 	}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/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 88874a3..b874628 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
@@ -35,7 +35,7 @@ public final class HtmlDocSerializerSession extends HtmlSerializerSession {
 
 	private final String title, description, branding, header, nav, aside, footer, cssUrl, noResultsMessage;
 	private final String[] css;
-	private final Map<String,String> links;
+	private final Map<String,Object> links;
 	private final boolean nowrap;
 	private final HtmlDocTemplate template;
 
@@ -158,7 +158,7 @@ public final class HtmlDocSerializerSession extends HtmlSerializerSession {
 	 * @return The {@link HtmlDocSerializerContext#HTMLDOC_links} setting value in this context.
 	 * 	<jk>null</jk> if not specified.  Never an empty map.
 	 */
-	public final Map<String,String> getLinks() {
+	public final Map<String,Object> getLinks() {
 		return links;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java
index 1853f00..3088e9c 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocTemplateBasic.java
@@ -112,14 +112,18 @@ public class HtmlDocTemplateBasic implements HtmlDocTemplate {
 			if (exists(nav))
 				w.append(2, nav).nl(2);
 		} else {
-			Map<String,String> htmlLinks = session.getLinks();
+			Map<String,Object> htmlLinks = session.getLinks();
 			boolean first = true;
 			if (htmlLinks != null) {
-				for (Map.Entry<String,String> e : htmlLinks.entrySet()) {
+				for (Map.Entry<String,Object> e : htmlLinks.entrySet()) {
+					String v = e.getValue().toString();
 					if (! first)
 						w.append(3, " - ").nl(3);
 					first = false;
-					w.oTag("a").attr("class", "link").attr("href", session.resolveUri(e.getValue()), true).cTag().text(e.getKey(), true).eTag("a");
+					if (v.startsWith("<"))
+						w.append(v);
+					else
+						w.oTag("a").attr("class", "link").attr("href", session.resolveUri(v), true).cTag().text(e.getKey(), true).eTag("a");
 				}
 			}
 		}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
index 3baf951..b91727c 100644
--- a/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
@@ -742,6 +742,26 @@ public final class StringUtils {
 	}
 
 	/**
+	 * Same as {@link #endsWith(String, char)} except check for multiple characters.
+	 *
+	 * @param s The string to check.  Can be <jk>null</jk>.
+	 * @param c The characters to check for.
+	 * @return <jk>true</jk> if the specified string is not <jk>null</jk> and ends with the specified character.
+	 */
+	public static boolean endsWith(String s, char...c) {
+		if (s != null) {
+			int i = s.length();
+			if (i > 0) {
+				char c2 = s.charAt(i-1);
+				for (char cc : c)
+					if (c2 == cc)
+						return true;
+			}
+		}
+		return false;
+	}
+
+	/**
 	 * Converts the specified number into a 4 hexadecimal characters.
 	 *
 	 * @param num The number to convert to hex.
@@ -1522,4 +1542,23 @@ public final class StringUtils {
 				return ss;
 		return null;
 	}
+
+	/**
+	 * Same as {@link String#indexOf(int)} except allows you to check for mulitiple characters.
+	 *
+	 * @param s The string to check.
+	 * @param c The characters to check for.
+	 * @return The index into the string that is one of the specified characters.
+	 */
+	public static int indexOf(String s, char...c) {
+		if (s == null)
+			return -1;
+		for (int i = 0; i < s.length(); i++) {
+			char c2 = s.charAt(i);
+			for (char cc : c)
+				if (c2 == cc)
+					return i;
+		}
+		return -1;
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
index 9d0ffee..5a4db6e 100644
--- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
+++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
@@ -68,6 +68,7 @@ public final class SerializerGroup {
 	private final Serializer[] mediaTypeSerializers;
 	private final List<Serializer> serializers;
 	private final PropertyStore propertyStore;
+	private final BeanContext beanContext;
 
 	/**
 	 * Constructor.
@@ -80,6 +81,7 @@ public final class SerializerGroup {
 	 */
 	public SerializerGroup(PropertyStore propertyStore, Serializer[] serializers) {
 		this.propertyStore = PropertyStore.create(propertyStore);
+		this.beanContext = propertyStore.getBeanContext();
 		this.serializers = Collections.unmodifiableList(new ArrayList<Serializer>(Arrays.asList(serializers)));
 
 		List<MediaType> lmt = new ArrayList<MediaType>();
@@ -204,4 +206,13 @@ public final class SerializerGroup {
 	public List<Serializer> getSerializers() {
 		return serializers;
 	}
+
+	/**
+	 * Returns a bean context with the same properties as this group.
+	 *
+	 * @return The bean context.
+	 */
+	public BeanContext getBeanContext() {
+		return beanContext;
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/09590092/juneau-core/src/main/java/org/apache/juneau/utils/PojoQuery.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/utils/PojoQuery.java b/juneau-core/src/main/java/org/apache/juneau/utils/PojoQuery.java
index e93f989..a4566e5 100644
--- a/juneau-core/src/main/java/org/apache/juneau/utils/PojoQuery.java
+++ b/juneau-core/src/main/java/org/apache/juneau/utils/PojoQuery.java
@@ -23,7 +23,7 @@ import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 
 /**
- * Designed to provide query/view/sort/paging filtering on tabular in-memory POJO models.
+ * Designed to provide search/view/sort/paging filtering on tabular in-memory POJO models.
  * <p>
  * It can also perform just view filtering on beans/maps.
  * <p>
@@ -38,47 +38,40 @@ import org.apache.juneau.internal.*;
  * Tabular POJO models can be thought of as tables of data.  For example, a list of the following beans...
  * <p class='bcode'>
  * 	<jk>public</jk> MyBean {
- * 		<jk>public int</jk> fi;
- * 		<jk>public</jk> String fs;
- * 		<jk>public</jk> Date fd;
+ * 		<jk>public int</jk> myInt;
+ * 		<jk>public</jk> String myString;
+ * 		<jk>public</jk> Date myDate;
  * 	}
  * <p>
  * 	... can be thought of a table containing the following columns...
  * <p>
  * 	<table class='styled code'>
- * 		<tr><th>fi</th><th>fs</th><th>fd</th></tr>
+ * 		<tr><th>myInt</th><th>myString</th><th>myDate</th></tr>
  * 		<tr><td>123</td><td>'foobar'</td><td>yyyy/MM/dd HH:mm:ss</td></tr>
  * 		<tr><td colspan=3>...</td></tr>
  * 	</table>
  * <p>
  * From this table, you can perform the following functions:
  * 	<ul class='spaced-list'>
- * 		<li>Query - Return only rows where a search pattern matches.
+ * 		<li>Search - Return only rows where a search pattern matches.
  * 		<li>View - Return only the specified subset of columns in the specified order.
  * 		<li>Sort - Sort the table by one or more columns.
- * 		<li>Page - Only return a subset of rows.
+ * 		<li>Position/limit - Only return a subset of rows.
  * 	</ul>
  *
- * <h5 class='topic'>Query</h5>
+ * <h5 class='topic'>Search</h5>
  * <p>
- * The query capabilites allow you to filter based on query patterns against
+ * The search capabilities allow you to filter based on query patterns against
  * 	strings, dates, and numbers.  Queries take the form of a Map with column names
  * 	as keys, and search patterns as values.  <br>
- * Search patterns can be either {@code Strings} or {@code Maps}.<br>
  * Multiple search patterns are ANDed (i.e. all patterns must match for the row to be returned).
  *
  * <h5 class='section'>Example:</h5>
  * <ul class='spaced-list'>
- * 	<li><tt>{fi:'123'}</tt> - Return only rows where the <tt>fi</tt> column is 123.
- * 	<li><tt>{fs:'foobar'}</tt> - Return only rows where the <tt>fs</tt> column is 'foobar'.
- * 	<li><tt>{fd:'2001'}</tt> - Return only rows where the <tt>fd</tt> column have dates in the year 2001.
- * 	<li><tt>{fs:'foobar'}</tt> - Return only rows where the <tt>fs</tt> column is 'foobar'.
- * 		and the <tt>fs</tt> column starts with <tt>"foo"</tt>.
+ * 	<li><tt>{myInt:'123'}</tt> - Return only rows where the <tt>myInt</tt> column is 123.
+ * 	<li><tt>{myString:'foobar'}</tt> - Return only rows where the <tt>myString</tt> column is 'foobar'.
+ * 	<li><tt>{myDate:'2001'}</tt> - Return only rows where the <tt>myDate</tt> column have dates in the year 2001.
  * </ul>
- * <p>
- * Search patterns can also be applied to lower level fields.  For example, the search term
- * 	<tt>{f1:{f2:{f3{'foobar'}}}</tt> means only return top level rows where the <tt>f1.getF2().getF3()</tt>
- * 	property is <tt>'foobar'</tt>.
  *
  * <h5 class='topic'>String Patterns</h5>
  * <p>
@@ -163,37 +156,28 @@ import org.apache.juneau.internal.*;
  *
  * <h6 class='topic'>Example view parameters:</h6>
  * <ul>
- * 	<li><tt>['f1']</tt> - Return only column 'f1'.
- * 	<li><tt>['f2','f1']</tt> - Return only columns 'f2' and 'f1'.
- * 	<li><tt>['f1',{f2:'f3'}]</tt> - Return only columns 'f1' and 'f2', but for 'f2' objects,
- * 		only show the 'f3' property.
+ * 	<li><tt>column1</tt> - Return only column 'column1'.
+ * 	<li><tt>column2, column1</tt> - Return only columns 'column2' and 'column1' in that order.
  * </ul>
  *
  * <h5 class='topic'>Sort</h5>
  * <p>
  * The sort capability allows you to sort values by the specified rows.<br>
- * The sort parameter is a list of either <tt>Strings</tt> or <tt>Maps</tt>.<br>
- * 	<tt>Strings</tt> represent column names to sort ascending.  If you want
- * 	to sort descending, you need to specify a <tt>Map</tt> of the form <tt>{colname:'d'}</tt>
+ * The sort parameter is a list of strings with an optional <js>'+'</js> or <js>'-'</js> suffix representing
+ * 	ascending and descending order accordingly.
  *
  * <h6 class='topic'>Example sort parameters:</h6>
  * <ul>
- * 	<li><tt>['f1']</tt> - Sort rows by column 'f1' ascending.
- * 	<li><tt>[{f1:'a'}]</tt> - Sort rows by column 'f1' ascending.
- * 	<li><tt>[{f1:'d'}]</tt> - Sort rows by column 'f1' descending.
- * 	<li><tt>[{f1:'a'},{f2:'d'}]</tt> - Sort rows by column 'f1' ascending, then 'f2' descending.
+ * 	<li><tt>column1</tt> - Sort rows by column 'column1' ascending.
+ * 	<li><tt>column1+</tt> - Sort rows by column 'column1' ascending.
+ * 	<li><tt>column1-</tt> - Sort rows by column 'column1' descending.
+ * 	<li><tt>column1, column2-</tt> - Sort rows by column 'column1' ascending, then 'column2' descending.
  * </ul>
  *
  * <h5 class='topic'>Paging</h5>
  * <p>
- * Use the <tt>pos</tt> and <tt>limit</tt> parameters to specify a subset of rows to
+ * Use the <tt>position</tt> and <tt>limit</tt> parameters to specify a subset of rows to
  * 	return.
- *
- * <h5 class='topic'>Other Notes</h5>
- * <ul class='spaced-list'>
- * 	<li>Calling <tt>filterMap()</tt> or <tt>filterCollection()</tt> always returns a new data
- * 		structure, so the methods can be called multiple times against the same input.
- * </ul>
  */
 @SuppressWarnings({"unchecked","rawtypes"})
 public final class PojoQuery {
@@ -215,37 +199,13 @@ public final class PojoQuery {
 	}
 
 	/**
-	 * Filters the input object as a map.
-	 *
-	 * @param view The list and order of properties to return from the map.  Values must be of type {@code String} or {@code Map}.
-	 * @return The filtered map
-	 */
-	public Map filterMap(List view) {
-
-		if (input == null)
-			return null;
-
-		if (! type.isMapOrBean())
-			throw new RuntimeException("Cannot call filterMap() on class type " + type);
-
-		Map m = (Map)replaceWithMutables(input);
-		doView(m, view);
-
-		return m;
-	}
-
-	/**
 	 * Filters the input object as a collection of maps.
 	 *
-	 * @param query The query attributes.  Keys must be column names and values must be of type {@code String} or {@code Map}.
-	 * @param view The view attributes.  Values must be of type {@code String} or {@code Map}.
-	 * @param sort The sort attributes.  Values must be of type {@code String} or {@code Map}.
-	 * @param pos The index into the list to start returning results from.  Default is {@code 0}.
-	 * @param limit The number of rows to return.  Default is all rows.
-	 * @param ignoreCase If <jk>true</jk>, then querying is case insensitive.  Default is <jk>false</jk>.
+	 * @param args The search arguments.
 	 * @return The filtered collection.
+	 * 	<br>Returns the unaltered input if the input is not a collection or array of objects.
 	 */
-	public List filterCollection(Map query, List view, List sort, int pos, int limit, boolean ignoreCase) {
+	public List filter(SearchArgs args) {
 
 		if (input == null)
 			return null;
@@ -253,31 +213,28 @@ public final class PojoQuery {
 		if (! type.isCollectionOrArray())
 			throw new RuntimeException("Cannot call filterCollection() on class type " + type);
 
-		if (view == null)
-			view = Collections.EMPTY_LIST;
-
-		if (sort == null)
-			sort = Collections.EMPTY_LIST;
-
 		// Create a new ObjectList
 		ObjectList l = (ObjectList)replaceWithMutables(input);
 
 		// Do the search
-		CollectionFilter filter = new CollectionFilter(query, ignoreCase);
+		CollectionFilter filter = new CollectionFilter(args.getSearch(), args.isIgnoreCase());
 		filter.doQuery(l);
 
 		// If sort or view isn't empty, then we need to make sure that all entries in the
 		// list are maps.
-		if ((! sort.isEmpty()) || (! view.isEmpty())) {
+		Map<String,Boolean> sort = args.getSort();
+		List<String> view = args.getView();
 
+		if ((! sort.isEmpty()) || (! view.isEmpty())) {
 			if (! sort.isEmpty())
 				doSort(l, sort);
-
 			if (! view.isEmpty())
 				doView(l, view);
 		}
 
 		// Do the paging.
+		int pos = args.getPosition();
+		int limit = args.getLimit();
 		if (pos != 0 || limit != 0) {
 			int end = (limit == 0 || limit+pos >= l.size()) ? l.size() : limit + pos;
 			ObjectList l2 = new DelegateList(((DelegateList)l).getClassMeta());
@@ -304,39 +261,22 @@ public final class PojoQuery {
 		if (cm.isMap() && o instanceof BeanMap) {
 			BeanMap bm = (BeanMap)o;
 			DelegateBeanMap dbm = new DelegateBeanMap(bm.getBean(), session);
-			for (BeanMapEntry e : (Set<BeanMapEntry>)bm.entrySet()) {
-				ClassMeta ct1 = e.getMeta().getClassMeta();
-				if (ct1.isCollectionOrArray() || ct1.isMapOrBean() || ct1.isObject())
-					dbm.put(e.getKey(), replaceWithMutables(e.getValue()));
-				else
-					dbm.addKey(e.getKey());
-			}
+			for (Object key : bm.keySet())
+				dbm.addKey(key.toString());
 			return dbm;
 		}
 		if (cm.isBean()) {
 			BeanMap bm = session.toBeanMap(o);
 			DelegateBeanMap dbm = new DelegateBeanMap(bm.getBean(), session);
-			for (BeanMapEntry e : (Set<BeanMapEntry>)bm.entrySet()) {
-				ClassMeta ct1 = e.getMeta().getClassMeta();
-				if (ct1.isCollectionOrArray() || ct1.isMapOrBean() || ct1.isObject()) {
-					Object val = null;
-					try {
-						val = e.getValue();
-					} catch (BeanRuntimeException ex) {
-						// Ignore.
-					}
-					dbm.put(e.getKey(), replaceWithMutables(val));
-				}
-				else
-					dbm.addKey(e.getKey());
-			}
+			for (Object key : bm.keySet())
+				dbm.addKey(key.toString());
 			return dbm;
 		}
 		if (cm.isMap()) {
 			Map m = (Map)o;
 			DelegateMap dm = new DelegateMap(session.getClassMetaForObject(m));
 			for (Map.Entry e : (Set<Map.Entry>)m.entrySet())
-				dm.put(e.getKey().toString(), replaceWithMutables(e.getValue()));
+				dm.put(e.getKey().toString(), e.getValue());
 			return dm;
 		}
 		if (cm.isArray()) {
@@ -348,24 +288,15 @@ public final class PojoQuery {
 	/*
 	 * Sorts the specified list by the sort list.
 	 */
-	private static void doSort(List list, List sortList) {
-
-		Map sort = new LinkedHashMap();
-		for (Object s : sortList) {
-			if (s instanceof String)
-				sort.put(s, "a");
-			else if (s instanceof Map) {
-				Map sm = (Map)s;
-				for (Map.Entry e : (Set<Map.Entry>)sm.entrySet())
-					sort.put(e.getKey(), e.getValue().toString().toLowerCase(Locale.ENGLISH));
-			}
-		}
+	private static void doSort(List list, Map<String,Boolean> sortList) {
 
-		// Do the sort.
-		List<String> columns = new ArrayList<String>(sort.keySet());
+		// We reverse the list and sort last to first.
+		List<String> columns = new ArrayList<String>(sortList.keySet());
 		Collections.reverse(columns);
+
 		for (final String c : columns) {
-			final boolean isDesc = startsWith(sort.get(c).toString(), 'd');
+			final boolean isDesc = sortList.get(c);
+
 			Comparator comp = new Comparator<Map>() {
 				@Override /* Comparator */
 				public int compare(Map m1, Map m2) {
@@ -386,8 +317,7 @@ public final class PojoQuery {
 	/*
 	 * Filters all but the specified view columns on all entries in the specified list.
 	 */
-	private void doView(List list, List view) {
-
+	private static void doView(List list, List<String> view) {
 		for (ListIterator i = list.listIterator(); i.hasNext();) {
 			Object o = i.next();
 			Map m = (Map)o;
@@ -398,48 +328,15 @@ public final class PojoQuery {
 	/*
 	 * Creates a new Map with only the entries specified in the view list.
 	 */
-	private void doView(Map m, List view) {
-		List<String> filterKeys = new LinkedList<String>();
-		for (Object v : view) {
-			if (v instanceof String) {
-				filterKeys.add(v.toString());
-			} else if (v instanceof ObjectMap) {
-				ObjectMap vm = (ObjectMap)v;
-				for (Map.Entry<String,Object> e : vm.entrySet()) {
-					String vmKey = e.getKey();
-					Object vmVal = e.getValue();
-					Object mv = m.get(vmKey);
-					filterKeys.add(vmKey);
-					if (vmVal instanceof List) {
-						List l = (List)vmVal;
-						if (mv instanceof List)
-							doView((List)mv, l);
-						else if (mv instanceof Map)
-							doView((Map)mv, l);
-					}
-				}
-			}
-		}
+	private static Map doView(Map m, List<String> view) {
 		if (m instanceof DelegateMap)
-			((DelegateMap)m).filterKeys(filterKeys);
+			((DelegateMap)m).filterKeys(view);
 		else
-			((DelegateBeanMap)m).filterKeys(filterKeys);
+			((DelegateBeanMap)m).filterKeys(view);
+		return m;
 	}
 
 
-	/*
-	 * Returns the appropriate IMatcher for the specified class type.
-	 */
-	private IMatcher getObjectMatcherForType(String queryString, boolean ignoreCase, ClassMeta cm) {
-		if (cm.isDate())
-			return new DateMatcher(queryString);
-		if (cm.isNumber())
-			return new NumberMatcher(queryString);
-		if (cm.isObject())
-			return new ObjectMatcher(queryString, ignoreCase);
-		return new StringMatcher(queryString, ignoreCase);
-	}
-
 	//====================================================================================================
 	// CollectionFilter
 	//====================================================================================================
@@ -480,18 +377,9 @@ public final class PojoQuery {
 		Map<String,IMatcher> entryMatchers = new HashMap<String,IMatcher>();
 
 		public MapMatcher(Map query, boolean ignoreCase) {
-			for (Map.Entry e : (Set<Map.Entry>)query.entrySet()) {
-				String key = e.getKey().toString();
-				Object value = e.getValue();
-				IMatcher matcher = null;
-				if (value instanceof String)
-					matcher = getObjectMatcherForType((String)value, ignoreCase, session.object());
-				else if (value instanceof ObjectMap)
-					matcher = new MapMatcher((ObjectMap)value, ignoreCase);
-				else
-					throw new RuntimeException("Invalid value type: " + value);
-				entryMatchers.put(key, matcher);
-			}
+			for (Map.Entry e : (Set<Map.Entry>)query.entrySet())
+				if (e.getKey() != null && e.getValue() != null)
+					entryMatchers.put(e.getKey().toString(), new ObjectMatcher(e.getValue().toString(), ignoreCase));
 		}
 
 		@Override /* IMatcher */
@@ -500,7 +388,12 @@ public final class PojoQuery {
 				return false;
 			for (Map.Entry<String,IMatcher> e : entryMatchers.entrySet()) {
 				String key = e.getKey();
-				Object val = m.get(key);
+				Object val = null;
+				if (m instanceof BeanMap) {
+					val = ((BeanMap)m).getRaw(key);
+				} else {
+					val = m.get(key);
+				}
 				if (! e.getValue().matches(val))
 					return false;
 			}
@@ -912,7 +805,7 @@ public final class PojoQuery {
 	 * @param pp Where parsing last left off.
 	 * @return An object represening a timestamp.
 	 */
-	protected CalendarP parseDate(String seg, ParsePosition pp) {
+	private CalendarP parseDate(String seg, ParsePosition pp) {
 
 		CalendarP cal = null;