This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 12fab71f36 Clean up juneau-rest-server
12fab71f36 is described below
commit 12fab71f36eb2998b31ed88a9027ea8e548c6ec3
Author: James Bognar <[email protected]>
AuthorDate: Thu Jan 1 12:09:24 2026 -0500
Clean up juneau-rest-server
---
.../juneau/rest/mock/MockServletRequest.java | 20 +-
.../java/org/apache/juneau/rest/RestSession.java | 13 +-
.../juneau/rest/httppart/RequestFormParams.java | 13 +-
.../org/apache/juneau/rest/util/RestUtils.java | 212 ++++++++++++++-------
.../apache/juneau/rest/util/RestUtils_Test.java | 18 +-
5 files changed, 190 insertions(+), 86 deletions(-)
diff --git
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
index e5bf497fa2..c3ecfaecca 100644
---
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
+++
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
@@ -393,8 +393,16 @@ public class MockServletRequest implements
HttpServletRequest {
@Override /* Overridden from HttpServletRequest */
public Map<String,String[]> getParameterMap() {
if ("POST".equalsIgnoreCase(method)) {
- if (formDataMap == null)
- formDataMap =
RestUtils.parseQuery(read(content));
+ if (formDataMap == null) {
+ var listMap =
RestUtils.parseQuery(read(content));
+ formDataMap = map();
+ for (var e : listMap.entrySet()) {
+ if (e.getValue() == null)
+ formDataMap.put(e.getKey(),
null);
+ else
+ formDataMap.put(e.getKey(),
array(e.getValue(), String.class));
+ }
+ }
return formDataMap;
}
return queryDataMap;
@@ -928,7 +936,13 @@ public class MockServletRequest implements
HttpServletRequest {
if (qs.indexOf('#') != -1)
qs = qs.substring(0, qs.indexOf('#'));
queryString = qs;
- queryDataMap.putAll(RestUtils.parseQuery(qs));
+ var listMap = RestUtils.parseQuery(qs);
+ for (var e : listMap.entrySet()) {
+ if (e.getValue() == null)
+ queryDataMap.put(e.getKey(), null);
+ else
+ queryDataMap.put(e.getKey(),
array(e.getValue(), String.class));
+ }
}
return this;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
index 7963dcf407..d41f1b867b 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
@@ -401,9 +401,16 @@ public class RestSession extends ContextSession {
*/
public Map<String,String[]> getQueryParams() {
if (queryParams == null) {
- if (req.getMethod().equalsIgnoreCase("POST"))
- queryParams =
RestUtils.parseQuery(req.getQueryString());
- else
+ if (req.getMethod().equalsIgnoreCase("POST")) {
+ var listMap =
RestUtils.parseQuery(req.getQueryString());
+ queryParams = map();
+ for (var e : listMap.entrySet()) {
+ if (e.getValue() == null)
+ queryParams.put(e.getKey(),
null);
+ else
+ queryParams.put(e.getKey(),
array(e.getValue(), String.class));
+ }
+ } else
queryParams = req.getParameterMap();
}
return queryParams;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestFormParams.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestFormParams.java
index c472a17f8c..9cbb9fd4c7 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestFormParams.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestFormParams.java
@@ -140,9 +140,16 @@ public class RequestFormParams extends
ArrayList<RequestFormParam> {
var c = (Collection<Part>)null;
RequestContent content = req.getContent();
- if (content.isLoaded() || !
req.getHeader(ContentType.class).orElse(ContentType.NULL).equalsIgnoreCase("multipart/form-data"))
- m = RestUtils.parseQuery(content.getReader());
- else {
+ if (content.isLoaded() || !
req.getHeader(ContentType.class).orElse(ContentType.NULL).equalsIgnoreCase("multipart/form-data"))
{
+ var listMap = RestUtils.parseQuery(content.getReader());
+ m = map();
+ for (var e : listMap.entrySet()) {
+ if (e.getValue() == null)
+ m.put(e.getKey(), null);
+ else
+ m.put(e.getKey(), array(e.getValue(),
String.class));
+ }
+ } else {
c = req.getHttpServletRequest().getParts();
if (c == null || c.isEmpty())
m =
req.getHttpServletRequest().getParameterMap();
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
index c3e7621ec2..0a690f7653 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
@@ -24,12 +24,10 @@ import static org.apache.juneau.commons.utils.Utils.*;
import java.io.*;
import java.util.*;
-import java.util.regex.*;
import org.apache.juneau.commons.utils.*;
import org.apache.juneau.json.*;
import org.apache.juneau.parser.*;
-import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.uon.*;
import jakarta.servlet.http.*;
@@ -88,8 +86,6 @@ public class RestUtils {
;
// @formatter:on
- private static final Pattern INDEXED_LINK_PATTERN =
Pattern.compile("(?s)(\\S*)\\[(\\d+)\\]\\:(.*)");
-
/**
* Returns readable text for an HTTP response code.
*
@@ -115,88 +111,168 @@ public class RestUtils {
}
/**
- * Parses a string that can consist of a simple string or JSON
object/array.
+ * Parses a string as JSON if it appears to be JSON, otherwise returns
the string as-is.
+ *
+ * <p>
+ * This method attempts to intelligently detect whether the input
string is JSON or plain text.
+ * If the string appears to be JSON (starts with <c>{</c>, <c>[</c>, or
other JSON indicators),
+ * it is parsed and returned as a Java object (<jk>Map</jk>,
<jk>List</jk>, <jk>String</jk>, <jk>Number</jk>, etc.).
+ * Otherwise, the original string is returned unchanged.
+ *
+ * <p>
+ * This is useful when processing input that could be either a JSON
value or a plain string,
+ * such as configuration values or user input that may or may not be
JSON-encoded.
*
- * @param value The string to parse.
- * @return The parsed value, or <jk>null</jk> if the input is null.
- * @throws ParseException Invalid JSON in string.
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// JSON object is parsed</jc>
+ * Object <jv>result1</jv> =
parseIfJson(<js>"{\"name\":\"John\"}"</js>);
+ * <jc>// Returns a Map with key "name" and value "John"</jc>
+ *
+ * <jc>// JSON array is parsed</jc>
+ * Object <jv>result2</jv> = parseIfJson(<js>"[1,2,3]"</js>);
+ * <jc>// Returns a List containing [1, 2, 3]</jc>
+ *
+ * <jc>// Plain string is returned as-is</jc>
+ * Object <jv>result3</jv> = parseIfJson(<js>"hello world"</js>);
+ * <jc>// Returns the string "hello world"</jc>
+ * </p>
+ *
+ * @param value The string to parse. Can be <jk>null</jk>.
+ * @return
+ * The parsed JSON object (if the string was JSON), or the
original string (if it was not JSON).
+ * Returns <jk>null</jk> if the input is <jk>null</jk>.
+ * <br>Return type can be: <jk>Map</jk>, <jk>List</jk>,
<jk>String</jk>, <jk>Number</jk>, <jk>Boolean</jk>, or <jk>null</jk>.
+ * @throws ParseException If the string appears to be JSON but contains
invalid JSON syntax.
*/
public static Object parseIfJson(String value) throws ParseException {
return isProbablyJson(value) ? JsonParser.DEFAULT.parse(value,
Object.class) : value;
}
/**
- * Parses a URL query string or form-data content.
+ * Parses a URL query string or form-data content from a string.
*
- * @param qs A reader or string containing the query string to parse.
- * @return A new map containing the parsed query.
+ * <p>
+ * Parses key-value pairs from a query string format (e.g.,
<c>key1=value1&key2=value2</c>).
+ * Supports multiple values for the same key, which are collected into
a <jk>List</jk>.
+ *
+ * <p>
+ * Special cases:
+ * <ul>
+ * <li>Empty or <jk>null</jk> strings return an empty map</li>
+ * <li>Keys without values (e.g., <c>key1&key2</c>) are stored
with <jk>null</jk> values</li>
+ * <li>Keys with empty values (e.g., <c>key=</c>) are stored with
empty strings</li>
+ * <li>Multiple occurrences of the same key append values to the
list</li>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * Map<String,List<String>> <jv>params</jv> =
parseQuery(<js>"f1=v1&f2=v2&f1=v3"</js>);
+ * <jv>params</jv>.get(<js>"f1"</js>); <jc>// Returns [v1,
v3]</jc>
+ * <jv>params</jv>.get(<js>"f2"</js>); <jc>// Returns [v2]</jc>
+ * </p>
+ *
+ * @param qs The query string to parse. Can be <jk>null</jk> or empty.
+ * @return A map of parameter names to lists of values. Returns an
empty map if the input is <jk>null</jk> or empty.
*/
- public static Map<String,String[]> parseQuery(Object qs) {
+ public static Map<String,List<String>> parseQuery(String qs) {
+ if (isEmpty(qs)) return Collections.emptyMap();
+ return parseQuery((Object)qs);
+ }
- try {
- var m = CollectionUtils.<String,String[]>map();
+ /**
+ * Parses a URL query string or form-data content from a reader.
+ *
+ * <p>
+ * Parses key-value pairs from a query string format (e.g.,
<c>key1=value1&key2=value2</c>).
+ * Supports multiple values for the same key, which are collected into
a <jk>List</jk>.
+ *
+ * <p>
+ * Special cases:
+ * <ul>
+ * <li><jk>null</jk> readers return an empty map</li>
+ * <li>Keys without values (e.g., <c>key1&key2</c>) are stored
with <jk>null</jk> values</li>
+ * <li>Keys with empty values (e.g., <c>key=</c>) are stored with
empty strings</li>
+ * <li>Multiple occurrences of the same key append values to the
list</li>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Parse from a reader</jc>
+ * Reader <jv>reader</jv> = <jk>new</jk>
StringReader(<js>"f1=v1&f2=v2"</js>);
+ * Map<String,List<String>> <jv>params</jv> =
parseQuery(<jv>reader</jv>);
+ * <jv>params</jv>.get(<js>"f1"</js>); <jc>// Returns [v1]</jc>
+ * </p>
+ *
+ * @param qs The reader containing the query string to parse. Can be
<jk>null</jk>.
+ * @return A map of parameter names to lists of values. Returns an
empty map if the input is <jk>null</jk>.
+ */
+ public static Map<String,List<String>> parseQuery(Reader qs) {
+ if (n(qs)) return Collections.emptyMap();
+ return parseQuery((Object)qs);
+ }
- if (qs == null || ((qs instanceof CharSequence) &&
isEmpty(s(qs))))
- return m;
+ private static Map<String,List<String>> parseQuery(Object qs) {
- try (var p = new ParserPipe(qs)) {
+ var m = CollectionUtils.<String,List<String>>map();
- // S1: Looking for attrName start.
- // S2: Found attrName start, looking for = or &
or end.
- // S3: Found =, looking for valStart or &.
- // S4: Found valStart, looking for & or end.
+ try (var p = new ParserPipe(qs)) {
- try (var r = new UonReader(p, true)) {
- var c = r.peekSkipWs();
- if (c == '?')
- r.read();
+ // S1: Looking for attrName start.
+ // S2: Found attrName start, looking for = or & or end.
+ // S3: Found =, looking for valStart or &.
+ // S4: Found valStart, looking for & or end.
- var state = S1;
- var currAttr = (String)null;
- while (c != -1) {
- c = r.read();
- if (state == S1) {
- if (c != -1) {
- r.unread();
- r.mark();
- state = S2;
- }
- } else if (state == S2) {
- if (c == -1) {
- add(m,
r.getMarked(), null);
- } else if (c ==
'\u0001') {
- add(m,
r.getMarked(0, -1), null);
- state = S1;
- } else if (c ==
'\u0002') {
- currAttr =
r.getMarked(0, -1);
- state = S3;
- }
- } else if (state == S3) {
- if (c == -1 || c ==
'\u0001') {
- add(m,
currAttr, "");
- state = S1;
- } else {
- if (c ==
'\u0002')
-
r.replace('=');
- r.unread();
- r.mark();
- state = S4;
- }
- } else if (state == S4) {
- if (c == -1) {
- add(m,
currAttr, r.getMarked());
- } else if (c ==
'\u0001') {
- add(m,
currAttr, r.getMarked(0, -1));
- state = S1;
- } else if (c ==
'\u0002') {
+ try (var r = new UonReader(p, true)) {
+ var c = r.peekSkipWs();
+ if (c == '?')
+ r.read();
+
+ var state = S1;
+ var currAttr = (String)null;
+ while (c != -1) {
+ c = r.read();
+ if (state == S1) {
+ if (c != -1) {
+ r.unread();
+ r.mark();
+ state = S2;
+ }
+ } else if (state == S2) {
+ if (c == -1) {
+ add(m, r.getMarked(),
null);
+ } else if (c == '\u0001') {
+ add(m, r.getMarked(0,
-1), null);
+ state = S1;
+ } else if (c == '\u0002') {
+ currAttr =
r.getMarked(0, -1);
+ state = S3;
+ }
+ } else if (state == S3) {
+ if (c == -1 || c == '\u0001') {
+ add(m, currAttr, "");
+ state = S1;
+ } else {
+ if (c == '\u0002')
r.replace('=');
- }
+ r.unread();
+ r.mark();
+ state = S4;
+ }
+ } else if (state == S4) {
+ if (c == -1) {
+ add(m, currAttr,
r.getMarked());
+ } else if (c == '\u0001') {
+ add(m, currAttr,
r.getMarked(0, -1));
+ state = S1;
+ } else if (c == '\u0002') {
+ r.replace('=');
}
}
}
-
- return m;
}
+
+ return m;
} catch (IOException e) {
throw toRex(e); // Should never happen.
}
@@ -273,7 +349,7 @@ public class RestUtils {
return value;
}
- private static void add(Map<String,String[]> m, String key, String val)
{
+ private static void add(Map<String,List<String>> m, String key, String
val) {
if (val == null) {
if (! m.containsKey(key))
m.put(key, null);
@@ -281,7 +357,7 @@ public class RestUtils {
m.compute(key, (k, existing) -> {
if (existing != null)
return addAll(existing, val);
- return a(val);
+ return list(val);
});
}
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/util/RestUtils_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/util/RestUtils_Test.java
index 4f3b878388..2e435da1c2 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/util/RestUtils_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/util/RestUtils_Test.java
@@ -86,21 +86,21 @@ class RestUtils_Test extends TestBase {
@Test void g01_testParseIntoSimpleMap() {
var s =
"?f1=,()=&f2a=$b(true)&f2b=true&f3a=$n(123)&f3b=123&f4=$s(foo)";
var m = parseQuery(s);
- assertEquals(",()=", m.get("f1")[0]);
- assertEquals("$b(true)", m.get("f2a")[0]);
- assertEquals("true", m.get("f2b")[0]);
- assertEquals("$n(123)", m.get("f3a")[0]);
- assertEquals("123", m.get("f3b")[0]);
- assertEquals("$s(foo)", m.get("f4")[0]);
+ assertEquals(",()=", m.get("f1").get(0));
+ assertEquals("$b(true)", m.get("f2a").get(0));
+ assertEquals("true", m.get("f2b").get(0));
+ assertEquals("$n(123)", m.get("f3a").get(0));
+ assertEquals("123", m.get("f3b").get(0));
+ assertEquals("$s(foo)", m.get("f4").get(0));
s = "f1=v1&=";
m = parseQuery(s);
- assertEquals("v1", m.get("f1")[0]);
- assertEquals("", m.get("")[0]);
+ assertEquals("v1", m.get("f1").get(0));
+ assertEquals("", m.get("").get(0));
s = "f1=v1&f2&f3";
m = parseQuery(s);
- assertEquals("v1", m.get("f1")[0]);
+ assertEquals("v1", m.get("f1").get(0));
assertTrue(m.containsKey("f2"));
assertTrue(m.containsKey("f3"));
assertNull(m.get("f2"));