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 aa8f207c57 Add support for setting serializer/parser session
properties as HTTP headers and query parameters
aa8f207c57 is described below
commit aa8f207c5763e4fc62d0c411143c964a8ff18062
Author: James Bognar <[email protected]>
AuthorDate: Sat Apr 4 15:39:19 2026 -0400
Add support for setting serializer/parser session properties as HTTP
headers and query parameters
---
.../juneau/commons/reflect/AnnotationInfo.java | 21 +++
.../juneau/commons/utils/CollectionUtils.java | 80 ++++++++
.../apache/juneau/commons/utils/StringUtils.java | 3 +
.../org/apache/juneau/rest/client/RestClient.java | 133 ++++++++++++++
.../org/apache/juneau/rest/client/RestRequest.java | 123 +++++++++++++
.../apache/juneau/rest/RestSharedConstants.java | 59 ++++++
.../apache/juneau/rest/mock/MockRestClient.java | 48 +++++
.../apache/juneau/rest/mock/MockRestRequest.java | 48 +++++
.../java/org/apache/juneau/rest/RestContext.java | 155 ++++++++++++++++
.../java/org/apache/juneau/rest/RestOpContext.java | 104 +++++++++++
.../java/org/apache/juneau/rest/RestRequest.java | 202 +++++++++++++++++++++
.../apache/juneau/rest/RestServerConstants.java | 53 ++++++
.../org/apache/juneau/rest/annotation/Rest.java | 56 ++++++
.../juneau/rest/annotation/RestAnnotation.java | 57 ++++++
.../apache/juneau/rest/annotation/RestDelete.java | 50 +++++
.../rest/annotation/RestDeleteAnnotation.java | 57 ++++++
.../org/apache/juneau/rest/annotation/RestGet.java | 50 +++++
.../juneau/rest/annotation/RestGetAnnotation.java | 57 ++++++
.../org/apache/juneau/rest/annotation/RestOp.java | 50 +++++
.../juneau/rest/annotation/RestOpAnnotation.java | 57 ++++++
.../apache/juneau/rest/annotation/RestOptions.java | 50 +++++
.../rest/annotation/RestOptionsAnnotation.java | 57 ++++++
.../apache/juneau/rest/annotation/RestPatch.java | 50 +++++
.../rest/annotation/RestPatchAnnotation.java | 57 ++++++
.../apache/juneau/rest/annotation/RestPost.java | 50 +++++
.../juneau/rest/annotation/RestPostAnnotation.java | 57 ++++++
.../org/apache/juneau/rest/annotation/RestPut.java | 50 +++++
.../juneau/rest/annotation/RestPutAnnotation.java | 57 ++++++
.../juneau/rest/config/BasicUniversalConfig.java | 41 +++++
.../juneau/rest/httppart/RequestContent.java | 2 +-
.../rest/processor/SerializedPojoProcessor.java | 2 +-
.../juneau/commons/utils/CollectionUtils_Test.java | 22 +++
.../org/apache/juneau/rest/NoInherit_Test.java | 168 +++++++++++++++++
.../rest/RestRequest_SessionProperties_Test.java | 158 ++++++++++++++++
.../rest/annotation/RestAnnotation_Test.java | 16 +-
.../rest/annotation/RestDeleteAnnotation_Test.java | 16 +-
.../rest/annotation/RestGetAnnotation_Test.java | 16 +-
.../rest/annotation/RestOpAnnotation_Test.java | 16 +-
.../rest/annotation/RestPostAnnotation_Test.java | 16 +-
.../rest/annotation/RestPutAnnotation_Test.java | 16 +-
.../rest/client/RestClient_Headers_Test.java | 10 +-
.../juneau/rest/client/RestClient_Query_Test.java | 10 +
42 files changed, 2385 insertions(+), 15 deletions(-)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationInfo.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationInfo.java
index 59e56cd831..2da9f91b57 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationInfo.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationInfo.java
@@ -127,6 +127,27 @@ public class AnnotationInfo<T extends Annotation> {
this.toString = memoize(this::findToString);
}
+ /**
+ * Returns the annotatable object (class, method, field, etc.) where
this annotation was found.
+ *
+ * @return The annotatable object where this annotation was found.
+ */
+ public Annotatable getAnnotatable() {
+ return annotatable;
+ }
+
+ /**
+ * Returns the annotatable object cast to the specified type.
+ *
+ * @param <A> The annotatable type.
+ * @param type The class to cast to.
+ * @return The annotatable element cast to the specified type.
+ * @throws ClassCastException If the annotatable element is not of the
specified type.
+ */
+ public <A extends Annotatable> A getAnnotatable(Class<A> type) {
+ return type.cast(annotatable);
+ }
+
/**
* Returns the annotation type of this annotation.
*
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
index 92d86c2ee6..2de555694a 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
@@ -129,6 +129,8 @@ import org.apache.juneau.commons.collections.*;
public class CollectionUtils {
// Argument name constants for assertArgNotNull
+ private static final String ARG_comparator = "comparator";
+ private static final String ARG_input = "input";
private static final String ARG_value = "value";
private static final String ARG_array = "array";
private static final String ARG_arrays = "arrays";
@@ -869,6 +871,26 @@ public class CollectionUtils {
return value;
}
+ /**
+ * Returns <jk>true</jk> if the specified collection is null or empty.
+ *
+ * @param c The collection to check.
+ * @return <jk>true</jk> if the specified collection is null or empty.
+ */
+ public static boolean isEmpty(Collection<?> c) {
+ return c == null || c.isEmpty();
+ }
+
+ /**
+ * Returns <jk>true</jk> if the specified map is null or empty.
+ *
+ * @param m The map to check.
+ * @return <jk>true</jk> if the specified map is null or empty.
+ */
+ public static boolean isEmpty(Map<?,?> m) {
+ return m == null || m.isEmpty();
+ }
+
/**
* Returns <jk>true</jk> if the specified array is null or has a length
of zero.
*
@@ -2015,6 +2037,64 @@ public class CollectionUtils {
return IntStream.range(0, value.size()).mapToObj(i ->
value.get(value.size() - 1 - i));
}
+ /**
+ * Removes negation tokens from a list.
+ *
+ * <p>
+ * A negation token is a string starting with <js>'-'</js> followed by
at least one character.
+ * When encountered, it removes the first prior occurrence of the
corresponding positive token.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * List<String> <jv>result</jv> =
<jsm>removeNegations</jsm>(list(<js>"a"</js>, <js>"b"</js>, <js>"-a"</js>,
<js>"c"</js>));
+ * <jc>// Produces: ["b", "c"]</jc>
+ * </p>
+ *
+ * @param input The input list. Cannot be <jk>null</jk>.
+ * @return The list with negation tokens applied, or the original list
if no negation tokens were present.
+ */
+ public static List<String> removeNegations(List<String> input) {
+ assertArgNotNull(ARG_input, input);
+ var hasNegation = false;
+ for (var token : input) {
+ if (token != null && token.length() > 1 &&
token.charAt(0) == '-') {
+ hasNegation = true;
+ break;
+ }
+ }
+ if (!hasNegation)
+ return input;
+ var out = new ArrayList<String>(input.size());
+ for (var token : input) {
+ if (token != null && token.length() > 1 &&
token.charAt(0) == '-')
+ out.remove(token.substring(1));
+ else
+ out.add(token);
+ }
+ return out;
+ }
+
+ /**
+ * Creates a {@link TreeSet} with a custom comparator from a collection
of elements.
+ *
+ * <p>
+ * Null elements in the collection are silently skipped.
+ *
+ * @param <E> The element type.
+ * @param comparator The comparator to use for ordering. Cannot be
<jk>null</jk>.
+ * @param elements The initial elements. Can be <jk>null</jk> (treated
as empty).
+ * @return A new {@link TreeSet} containing all non-null elements from
the collection.
+ */
+ public static <E> SortedSet<E> treeSet(Comparator<? super E>
comparator, Collection<? extends E> elements) {
+ assertArgNotNull(ARG_comparator, comparator);
+ var s = new TreeSet<E>(comparator);
+ if (elements != null)
+ for (var e : elements)
+ if (e != null)
+ s.add(e);
+ return s;
+ }
+
/**
* Shortcut for creating a modifiable set out of an array of values.
*
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
index b6620512cf..47cd29bde7 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
@@ -77,6 +77,9 @@ public class StringUtils {
/** Zero-length string constant. */
public static final String EMPTY = "";
+ /** Zero-length string array constant. */
+ public static final String[] EMPTY_STRING_ARRAY = new String[0];
+
/** Characters allowed at the beginning of a numeric literal. */
public static final AsciiSet FIRST_NUMBER_CHARS =
AsciiSet.of("+-.#0123456789");
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index e5a2a49701..4b2b2496a8 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -29,6 +29,7 @@ import static org.apache.juneau.http.HttpHeaders.*;
import static org.apache.juneau.http.HttpMethod.*;
import static org.apache.juneau.http.HttpParts.*;
import static org.apache.juneau.httppart.HttpPartType.*;
+import static org.apache.juneau.rest.RestSharedConstants.*;
import static org.apache.juneau.rest.client.RestOperation.*;
import java.io.*;
@@ -2552,6 +2553,72 @@ public class RestClient extends BeanContextable
implements HttpClient, Closeable
return this;
}
+ /**
+ * Sets a default request header with serializer session
options (JSON5 map) for all requests.
+ *
+ * <p>
+ * This is a shortcut for <c>headersDefault(basicHeader({@link
org.apache.juneau.rest.RestSharedConstants#HEADER_JuneauSerializerOptions},
<jv>json5</jv>))</c>.
+ *
+ * @param json5
+ * Serializer session options in JSON5 form.
+ * <br>Can be <jk>null</jk> (default is not set by this
method).
+ * @return This object.
+ */
+ public Builder serializerSessionOptionsHeader(String json5) {
+ if (json5 != null)
+
headersDefault(basicHeader(HEADER_JuneauSerializerOptions, json5));
+ return this;
+ }
+
+ /**
+ * Sets a default request header with parser session options
(JSON5 map) for all requests.
+ *
+ * <p>
+ * This is a shortcut for <c>headersDefault(basicHeader({@link
org.apache.juneau.rest.RestSharedConstants#HEADER_JuneauParserOptions},
<jv>json5</jv>))</c>.
+ *
+ * @param json5
+ * Parser session options in JSON5 form.
+ * <br>Can be <jk>null</jk> (default is not set by this
method).
+ * @return This object.
+ */
+ public Builder parserSessionOptionsHeader(String json5) {
+ if (json5 != null)
+
headersDefault(basicHeader(HEADER_JuneauParserOptions, json5));
+ return this;
+ }
+
+ /**
+ * Convenience for {@link
#serializerSessionOptionsHeader(String)} using a map serialized with JSON5.
+ *
+ * @param properties
+ * Property names and values for serializer session
options.
+ * <br>Can be <jk>null</jk> (treated as an empty map).
+ * @return This object.
+ */
+ public Builder serializerSessionOptionsHeader(Map<String,?>
properties) {
+ try {
+ return
serializerSessionOptionsHeader(isEmpty(properties) ? null :
Json5.of(properties));
+ } catch (SerializeException e) {
+ throw rex(e, "Could not serialize serializer
session options header");
+ }
+ }
+
+ /**
+ * Convenience for {@link #parserSessionOptionsHeader(String)}
using a map serialized with JSON5.
+ *
+ * @param properties
+ * Property names and values for parser session options.
+ * <br>Can be <jk>null</jk> (treated as an empty map).
+ * @return This object.
+ */
+ public Builder parserSessionOptionsHeader(Map<String,?>
properties) {
+ try {
+ return
parserSessionOptionsHeader(isEmpty(properties) ? null : Json5.of(properties));
+ } catch (SerializeException e) {
+ throw rex(e, "Could not serialize parser
session options header");
+ }
+ }
+
/**
* Convenience method for specifying HTML as the marshalling
transmission media type.
*
@@ -4639,6 +4706,72 @@ public class RestClient extends BeanContextable
implements HttpClient, Closeable
return this;
}
+ /**
+ * Sets a default query parameter with serializer session
options (UON map) for all requests.
+ *
+ * <p>
+ * This is a shortcut for <c>queryDataDefault(stringPart({@link
org.apache.juneau.rest.RestSharedConstants#QUERY_juneauSerializerOptions},
<jv>uon</jv>))</c>.
+ *
+ * @param uon
+ * Serializer session options in UON form.
+ * <br>Can be <jk>null</jk> (default is not set by this
method).
+ * @return This object.
+ */
+ public Builder serializerSessionOptionsQueryDefault(String uon)
{
+ if (uon != null)
+
queryDataDefault(stringPart(QUERY_juneauSerializerOptions, uon));
+ return this;
+ }
+
+ /**
+ * Sets a default query parameter with parser session options
(UON map) for all requests.
+ *
+ * <p>
+ * This is a shortcut for <c>queryDataDefault(stringPart({@link
org.apache.juneau.rest.RestSharedConstants#QUERY_juneauParserOptions},
<jv>uon</jv>))</c>.
+ *
+ * @param uon
+ * Parser session options in UON form.
+ * <br>Can be <jk>null</jk> (default is not set by this
method).
+ * @return This object.
+ */
+ public Builder parserSessionOptionsQueryDefault(String uon) {
+ if (uon != null)
+
queryDataDefault(stringPart(QUERY_juneauParserOptions, uon));
+ return this;
+ }
+
+ /**
+ * Convenience for {@link
#serializerSessionOptionsQueryDefault(String)} using {@link
org.apache.juneau.marshaller.Uon#of(Object)}.
+ *
+ * @param properties
+ * Property names and values for serializer session
options.
+ * <br>Can be <jk>null</jk> (treated as an empty map).
+ * @return This object.
+ */
+ public Builder
serializerSessionOptionsQueryDefault(Map<String,?> properties) {
+ try {
+ return
serializerSessionOptionsQueryDefault(isEmpty(properties) ? null :
Uon.of(properties));
+ } catch (SerializeException e) {
+ throw rex(e, "Could not serialize serializer
session options query default");
+ }
+ }
+
+ /**
+ * Convenience for {@link
#parserSessionOptionsQueryDefault(String)} using {@link
org.apache.juneau.marshaller.Uon#of(Object)}.
+ *
+ * @param properties
+ * Property names and values for parser session options.
+ * <br>Can be <jk>null</jk> (treated as an empty map).
+ * @return This object.
+ */
+ public Builder parserSessionOptionsQueryDefault(Map<String,?>
properties) {
+ try {
+ return
parserSessionOptionsQueryDefault(isEmpty(properties) ? null :
Uon.of(properties));
+ } catch (SerializeException e) {
+ throw rex(e, "Could not serialize parser
session options query default");
+ }
+ }
+
/**
* <i><l>WriterSerializer</l> configuration property: </i>
Quote character.
*
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
index f1d5799057..1d5bff26a2 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
@@ -18,6 +18,7 @@ package org.apache.juneau.rest.client;
import static org.apache.juneau.commons.utils.AssertionUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import org.apache.juneau.rest.RestSharedConstants;
import static org.apache.juneau.commons.utils.IoUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
@@ -1692,6 +1693,128 @@ public class RestRequest extends BeanSession implements
HttpUriRequest, Configur
return
serializer(PlainTextSerializer.class).parser(PlainTextParser.class);
}
+ /**
+ * Sets the {@code X-Juneau-Serializer-Options} header for this request.
+ *
+ * <p>
+ * The value is a JSON5 object whose keys are serializer session
property names and values are the overrides.
+ * Example: <js>"{escapeSolidus:true,maxIndent:4}"</js>.
+ *
+ * @param json5
+ * Serializer session options as JSON5. Can be <jk>null</jk>
(header is not set).
+ * @return This object.
+ */
+ public RestRequest serializerSessionOptionsHeader(String json5) {
+ return
header(RestSharedConstants.HEADER_JuneauSerializerOptions, json5);
+ }
+
+ /**
+ * Sets the {@code X-Juneau-Parser-Options} header for this request.
+ *
+ * <p>
+ * The value is a JSON5 object whose keys are parser session property
names and values are the overrides.
+ *
+ * @param json5
+ * Parser session options as JSON5. Can be <jk>null</jk> (header
is not set).
+ * @return This object.
+ */
+ public RestRequest parserSessionOptionsHeader(String json5) {
+ return header(RestSharedConstants.HEADER_JuneauParserOptions,
json5);
+ }
+
+ /**
+ * Convenience for {@link #serializerSessionOptionsHeader(String)}
using a map serialized with {@link
org.apache.juneau.marshaller.Json5#of(Object)}.
+ *
+ * @param properties
+ * Property map for serializer session options. Can be
<jk>null</jk> (treated as empty).
+ * @return This object.
+ */
+ public RestRequest serializerSessionOptionsHeader(Map<String,?>
properties) {
+ if (isEmpty(properties))
+ return this;
+ try {
+ return
header(RestSharedConstants.HEADER_JuneauSerializerOptions,
org.apache.juneau.marshaller.Json5.of(properties));
+ } catch (org.apache.juneau.serializer.SerializeException e) {
+ throw new RuntimeException("Could not serialize
serializer session options header", e);
+ }
+ }
+
+ /**
+ * Convenience for {@link #parserSessionOptionsHeader(String)} using a
map serialized with {@link org.apache.juneau.marshaller.Json5#of(Object)}.
+ *
+ * @param properties
+ * Property map for parser session options. Can be <jk>null</jk>
(treated as empty).
+ * @return This object.
+ */
+ public RestRequest parserSessionOptionsHeader(Map<String,?> properties)
{
+ if (isEmpty(properties))
+ return this;
+ try {
+ return
header(RestSharedConstants.HEADER_JuneauParserOptions,
org.apache.juneau.marshaller.Json5.of(properties));
+ } catch (org.apache.juneau.serializer.SerializeException e) {
+ throw new RuntimeException("Could not serialize parser
session options header", e);
+ }
+ }
+
+ /**
+ * Sets the {@code juneauSerializerOptions} query parameter for this
request (UON-encoded map).
+ *
+ * <p>
+ * Example: <js>"(escapeSolidus=true,maxIndent=4)"</js>.
+ *
+ * @param uon
+ * Serializer session options in UON form. Can be <jk>null</jk>
(parameter is not set).
+ * @return This object.
+ */
+ public RestRequest serializerSessionOptionsQuery(String uon) {
+ return
queryData(RestSharedConstants.QUERY_juneauSerializerOptions, uon);
+ }
+
+ /**
+ * Sets the {@code juneauParserOptions} query parameter for this
request (UON-encoded map).
+ *
+ * @param uon
+ * Parser session options in UON form. Can be <jk>null</jk>
(parameter is not set).
+ * @return This object.
+ */
+ public RestRequest parserSessionOptionsQuery(String uon) {
+ return queryData(RestSharedConstants.QUERY_juneauParserOptions,
uon);
+ }
+
+ /**
+ * Convenience for {@link #serializerSessionOptionsQuery(String)} using
{@link org.apache.juneau.marshaller.Uon#of(Object)}.
+ *
+ * @param properties
+ * Property map for serializer session options. Can be
<jk>null</jk> (treated as empty).
+ * @return This object.
+ */
+ public RestRequest serializerSessionOptionsQuery(Map<String,?>
properties) {
+ if (isEmpty(properties))
+ return this;
+ try {
+ return
serializerSessionOptionsQuery(org.apache.juneau.marshaller.Uon.of(properties));
+ } catch (org.apache.juneau.serializer.SerializeException e) {
+ throw new RuntimeException("Could not serialize
serializer session options query", e);
+ }
+ }
+
+ /**
+ * Convenience for {@link #parserSessionOptionsQuery(String)} using
{@link org.apache.juneau.marshaller.Uon#of(Object)}.
+ *
+ * @param properties
+ * Property map for parser session options. Can be <jk>null</jk>
(treated as empty).
+ * @return This object.
+ */
+ public RestRequest parserSessionOptionsQuery(Map<String,?> properties) {
+ if (isEmpty(properties))
+ return this;
+ try {
+ return
parserSessionOptionsQuery(org.apache.juneau.marshaller.Uon.of(properties));
+ } catch (org.apache.juneau.serializer.SerializeException e) {
+ throw new RuntimeException("Could not serialize parser
session options query", e);
+ }
+ }
+
/**
* Sets the protocol version for this request.
*
diff --git
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/rest/RestSharedConstants.java
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/rest/RestSharedConstants.java
new file mode 100644
index 0000000000..c6a7fc1c47
--- /dev/null
+++
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/rest/RestSharedConstants.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+/**
+ * Static constants shared across Juneau REST modules (for example {@code
juneau-rest-server},
+ * {@code juneau-rest-client}, and {@code juneau-rest-mock}) so wire names and
other cross-tier
+ * strings stay aligned.
+ *
+ * <p>
+ * Add more {@code public static final} fields here when server and client (or
other REST modules)
+ * must agree on the same literal values. Server-only constants belong in
+ * {@code RestServerConstants} in {@code juneau-rest-server}.
+ */
+@SuppressWarnings({
+ "java:S115", // Names use HEADER_/QUERY_ + camelCase to mirror wire
identifiers; not strict UPPER_SNAKE_CASE
+})
+public final class RestSharedConstants {
+
+ private RestSharedConstants() {}
+
+ /**
+ * Serializer session options request header (JSON5 object format).
+ *
+ * <p>
+ * HTTP header names are case-insensitive; use this spelling in OpenAPI
and examples.
+ * Example value: <js>{escapeSolidus:true,maxIndent:4}</js>
+ */
+ public static final String HEADER_JuneauSerializerOptions =
"X-Juneau-Serializer-Options";
+
+ /**
+ * Parser session options request header (JSON5 object format).
+ *
+ * <p>
+ * HTTP header names are case-insensitive; use this spelling in OpenAPI
and examples.
+ * Example value: <js>{trimStrings:true}</js>
+ */
+ public static final String HEADER_JuneauParserOptions =
"X-Juneau-Parser-Options";
+
+ /** Serializer session options query parameter (UON-encoded map
format). Example: <js>(maxIndent=4,sortMaps=true)</js> */
+ public static final String QUERY_juneauSerializerOptions =
"juneauSerializerOptions";
+
+ /** Parser session options query parameter (UON-encoded map format).
Example: <js>(trimStrings=true)</js> */
+ public static final String QUERY_juneauParserOptions =
"juneauParserOptions";
+}
diff --git
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
index ee3204f257..62dea237a7 100644
---
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
+++
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
@@ -320,6 +320,54 @@ public class MockRestClient extends RestClient implements
HttpClientConnection {
return this;
}
+ @Override /* Overridden from Builder */
+ public Builder serializerSessionOptionsHeader(String json5) {
+ super.serializerSessionOptionsHeader(json5);
+ return this;
+ }
+
+ @Override /* Overridden from Builder */
+ public Builder
serializerSessionOptionsHeader(java.util.Map<String,?> properties) {
+ super.serializerSessionOptionsHeader(properties);
+ return this;
+ }
+
+ @Override /* Overridden from Builder */
+ public Builder parserSessionOptionsHeader(String json5) {
+ super.parserSessionOptionsHeader(json5);
+ return this;
+ }
+
+ @Override /* Overridden from Builder */
+ public Builder
parserSessionOptionsHeader(java.util.Map<String,?> properties) {
+ super.parserSessionOptionsHeader(properties);
+ return this;
+ }
+
+ @Override /* Overridden from Builder */
+ public Builder serializerSessionOptionsQueryDefault(String uon)
{
+ super.serializerSessionOptionsQueryDefault(uon);
+ return this;
+ }
+
+ @Override /* Overridden from Builder */
+ public Builder
serializerSessionOptionsQueryDefault(java.util.Map<String,?> properties) {
+ super.serializerSessionOptionsQueryDefault(properties);
+ return this;
+ }
+
+ @Override /* Overridden from Builder */
+ public Builder parserSessionOptionsQueryDefault(String uon) {
+ super.parserSessionOptionsQueryDefault(uon);
+ return this;
+ }
+
+ @Override /* Overridden from Builder */
+ public Builder
parserSessionOptionsQueryDefault(java.util.Map<String,?> properties) {
+ super.parserSessionOptionsQueryDefault(properties);
+ return this;
+ }
+
@Override /* Overridden from Builder */
public Builder annotations(Annotation...values) {
super.annotations(values);
diff --git
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestRequest.java
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestRequest.java
index 118465356f..5f750e067b 100644
---
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestRequest.java
+++
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestRequest.java
@@ -806,6 +806,54 @@ public class MockRestRequest extends
org.apache.juneau.rest.client.RestRequest {
return this;
}
+ @Override /* Overridden from RestRequest */
+ public MockRestRequest serializerSessionOptionsHeader(String json5) {
+ super.serializerSessionOptionsHeader(json5);
+ return this;
+ }
+
+ @Override /* Overridden from RestRequest */
+ public MockRestRequest serializerSessionOptionsHeader(Map<String,?>
properties) {
+ super.serializerSessionOptionsHeader(properties);
+ return this;
+ }
+
+ @Override /* Overridden from RestRequest */
+ public MockRestRequest serializerSessionOptionsQuery(String uon) {
+ super.serializerSessionOptionsQuery(uon);
+ return this;
+ }
+
+ @Override /* Overridden from RestRequest */
+ public MockRestRequest serializerSessionOptionsQuery(Map<String,?>
properties) {
+ super.serializerSessionOptionsQuery(properties);
+ return this;
+ }
+
+ @Override /* Overridden from RestRequest */
+ public MockRestRequest parserSessionOptionsHeader(String json5) {
+ super.parserSessionOptionsHeader(json5);
+ return this;
+ }
+
+ @Override /* Overridden from RestRequest */
+ public MockRestRequest parserSessionOptionsHeader(Map<String,?>
properties) {
+ super.parserSessionOptionsHeader(properties);
+ return this;
+ }
+
+ @Override /* Overridden from RestRequest */
+ public MockRestRequest parserSessionOptionsQuery(String uon) {
+ super.parserSessionOptionsQuery(uon);
+ return this;
+ }
+
+ @Override /* Overridden from RestRequest */
+ public MockRestRequest parserSessionOptionsQuery(Map<String,?>
properties) {
+ super.parserSessionOptionsQuery(properties);
+ return this;
+ }
+
@Override /* Overridden from RestRequest */
public MockRestRequest protocolVersion(ProtocolVersion version) {
super.protocolVersion(version);
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 96640c41a9..c681f7cf71 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -23,6 +23,7 @@ import static
org.apache.juneau.commons.utils.AssertionUtils.*;
import static org.apache.juneau.commons.utils.ClassUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
import static org.apache.juneau.commons.utils.PredicateUtils.*;
+import static org.apache.juneau.rest.RestServerConstants.*;
import static org.apache.juneau.commons.utils.IoUtils.*;
import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
@@ -49,6 +50,7 @@ import org.apache.juneau.*;
import org.apache.juneau.bean.swagger.Swagger;
import org.apache.juneau.commons.collections.*;
import org.apache.juneau.commons.collections.FluentMap;
+import org.apache.juneau.commons.function.Memoizer;
import org.apache.juneau.commons.lang.*;
import org.apache.juneau.commons.logging.Logger;
import org.apache.juneau.commons.reflect.*;
@@ -5184,6 +5186,159 @@ public class RestContext extends Context {
return RestSession.create(this);
}
+
//---------------------------------------------------------------------------------------------
+ // Memoized allowlist fields
+
//---------------------------------------------------------------------------------------------
+
+ /**
+ * Memoized value of the {@code noInherit} annotation attribute from
the nearest {@code @Rest} annotation.
+ *
+ * <p>
+ * {@code noInherit} itself is never inherited; it only applies to the
{@code @Rest} that declares it.
+ */
+ private final Memoizer<SortedSet<String>> noInherit = memoizer(() ->
+ getRestAnnotation()
+ .map(x ->
x.getStringArray("noInherit").orElse(StringUtils.EMPTY_STRING_ARRAY))
+ .map(x -> treeSet(String.CASE_INSENSITIVE_ORDER,
resolveCdl(x).toList()))
+ .orElseGet(Collections::emptySortedSet)
+ );
+
+ /**
+ * Memoized list of every {@link Rest} annotation on the resource class
and its supertypes, in child-to-parent order.
+ */
+ private final Memoizer<List<AnnotationInfo<Rest>>> restAnnotations =
memoizer(() ->
+ getAnnotationProvider().find(Rest.class,
ClassInfo.of(getResourceClass()))
+ );
+
+ /**
+ * Memoized parser session-option keys from {@code
@Rest(allowedParserOptions)}, after SVL resolution and comma expansion.
+ *
+ * <p>
+ * When inheritance is not blocked, keys from {@link #parentContext}
are included first, then this resource's own tokens.
+ * Leading-hyphen tokens (e.g. {@code -foo}) remove earlier positive
tokens.
+ */
+ private final Memoizer<SortedSet<String>> allowedParserOptions =
memoizer(this::findAllowedParserOptions);
+
+ private SortedSet<String> findAllowedParserOptions() {
+ var l = new ArrayList<String>();
+ var p = PROPERTY_allowedParserOptions;
+ if (isInherited(p) && parentContext != null)
+ l.addAll(parentContext.getAllowedParserOptions());
+ getRestAnnotationsForProperty(p).forEach(x ->
resolveCdl(x.getStringArray(p)).forEach(l::add));
+ return
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER,
removeNegations(l)));
+ }
+
+ /**
+ * Memoized serializer session-option keys from {@code
@Rest(allowedSerializerOptions)}, after SVL resolution and comma expansion.
+ *
+ * <p>
+ * When inheritance is not blocked, keys from {@link #parentContext}
are included first, then this resource's own tokens.
+ * Leading-hyphen tokens remove earlier positive tokens.
+ */
+ private final Memoizer<SortedSet<String>> allowedSerializerOptions =
memoizer(this::findAllowedSerializerOptions);
+
+ private SortedSet<String> findAllowedSerializerOptions() {
+ var l = new ArrayList<String>();
+ var p = PROPERTY_allowedSerializerOptions;
+ if (isInherited(p) && parentContext != null)
+ l.addAll(parentContext.getAllowedSerializerOptions());
+ getRestAnnotationsForProperty(p).forEach(x ->
resolveCdl(x.getStringArray(p)).forEach(l::add));
+ return
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER,
removeNegations(l)));
+ }
+
+ private Stream<AnnotationInfo<Rest>>
getRestAnnotationsForProperty(String name) {
+ var annotations = getRestAnnotations();
+ var cutoff = annotations.size();
+ for (var i = 0; i < annotations.size(); i++) {
+ if
(resolveCdl(annotations.get(i).getStringArray(PROPERTY_noInherit)).anyMatch(name::equalsIgnoreCase))
{
+ cutoff = i + 1;
+ break;
+ }
+ }
+ return rstream(annotations.subList(0, cutoff));
+ }
+
+ /**
+ * Returns all {@link Rest} annotations on the resource class
hierarchy, in child-to-parent order.
+ *
+ * @return An unmodifiable list of {@link AnnotationInfo} for {@link
Rest}, never {@code null}.
+ */
+ protected List<AnnotationInfo<Rest>> getRestAnnotations() {
+ return restAnnotations.get();
+ }
+
+ /**
+ * Returns the nearest {@link Rest} annotation on this resource.
+ *
+ * @return An {@link Optional} containing the first (most-derived)
{@link Rest} {@link AnnotationInfo}.
+ */
+ protected Optional<AnnotationInfo<Rest>> getRestAnnotation() {
+ return getRestAnnotations().stream().findFirst();
+ }
+
+ /**
+ * Returns {@code true} if values for the given annotation attribute
may be inherited from {@link #parentContext}.
+ *
+ * <p>
+ * Inheritance is blocked when the nearest {@code @Rest(noInherit)}
lists the property name (case-insensitive).
+ *
+ * @param property The annotation attribute name (e.g. {@code
"allowedSerializerOptions"}).
+ * @return {@code true} if parent values should be included.
+ */
+ protected boolean isInherited(String property) {
+ return RestContext.this.parentContext != null &&
!noInherit.get().contains(property);
+ }
+
+ /**
+ * Resolves comma-delimited annotation values with SVL variable
substitution.
+ *
+ * @param values Raw annotation attribute values.
+ * @return A stream of trimmed, non-blank tokens.
+ */
+ private Stream<String> resolveCdl(String...values) {
+ if (values == null || values.length == 0)
+ return Stream.empty();
+ return Arrays.stream(values)
+ .filter(Objects::nonNull)
+ .map(this::resolve)
+ .map(StringUtils::split)
+ .flatMap(Collection::stream)
+ .map(String::trim)
+ .filter(StringUtils::isNotBlank);
+ }
+
+ private Stream<String> resolveCdl(Optional<String[]> values) {
+ return values.isEmpty() ? Stream.empty() :
resolveCdl(values.get());
+ }
+
+ /**
+ * Resolves SVL variables in the given string.
+ *
+ * @param s The raw string. Can be {@code null}.
+ * @return The resolved string.
+ */
+ protected String resolve(String s) {
+ return getVarResolver().resolve(s);
+ }
+
+ /**
+ * Returns the parser session-option keys allowed for this resource.
+ *
+ * @return An unmodifiable case-insensitive sorted set, never {@code
null}.
+ */
+ public SortedSet<String> getAllowedParserOptions() {
+ return allowedParserOptions.get();
+ }
+
+ /**
+ * Returns the serializer session-option keys allowed for this resource.
+ *
+ * @return An unmodifiable case-insensitive sorted set, never {@code
null}.
+ */
+ public SortedSet<String> getAllowedSerializerOptions() {
+ return allowedSerializerOptions.get();
+ }
+
/**
* Called during servlet destruction to invoke all {@link RestDestroy}
methods.
*/
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
index 2c0fabf8bb..5ba6e9b103 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
@@ -22,6 +22,7 @@ import static
org.apache.juneau.commons.utils.CollectionUtils.*;
import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
+import static org.apache.juneau.rest.RestServerConstants.*;
import static org.apache.juneau.http.HttpHeaders.*;
import static org.apache.juneau.http.HttpParts.*;
import static org.apache.juneau.httppart.HttpPartType.*;
@@ -32,12 +33,14 @@ import java.lang.reflect.Method;
import java.nio.charset.*;
import java.util.*;
import java.util.concurrent.*;
+import java.util.stream.*;
import java.util.function.*;
import org.apache.http.*;
import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.commons.collections.*;
import org.apache.juneau.commons.collections.FluentMap;
+import org.apache.juneau.commons.function.Memoizer;
import org.apache.juneau.commons.lang.*;
import org.apache.juneau.commons.reflect.*;
import org.apache.juneau.commons.utils.*;
@@ -2179,6 +2182,107 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
protected final String httpMethod;
protected final UrlPathMatcher[] pathMatchers;
+
//-----------------------------------------------------------------------------------------------------------------
+ // Memoized allowlist fields
+
//-----------------------------------------------------------------------------------------------------------------
+
+ /** Memoized all {@link RestOp}-group annotations on this method,
child-to-parent order. */
+ private final Memoizer<List<AnnotationInfo<?>>> restOpAnnotations =
memoizer(this::findRestOpAnnotations);
+
+ private List<AnnotationInfo<?>> findRestOpAnnotations() {
+ return context.getAnnotationProvider().find(mi, SELF,
MATCHING_METHODS).stream()
+ .filter(ai -> ai.isInGroup(RestOp.class))
+ .toList();
+ }
+
+ /** Memoized aggregated {@code noInherit} keys from all RestOp-group
annotations on this operation. */
+ private final Memoizer<SortedSet<String>> noInheritOp =
memoizer(this::findNoInheritOp);
+
+ private SortedSet<String> findNoInheritOp() {
+ var l = getRestOpAnnotations().stream()
+ .map(ai ->
ai.getStringArray("noInherit").orElse(StringUtils.EMPTY_STRING_ARRAY))
+ .flatMap(arr -> resolveCdl(arr))
+ .toList();
+ return
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, l));
+ }
+
+ /** Memoized effective allowed parser option keys for this operation. */
+ private final Memoizer<SortedSet<String>> allowedParserOptions =
memoizer(this::findAllowedParserOptions);
+
+ private SortedSet<String> findAllowedParserOptions() {
+ var l = new ArrayList<String>();
+ var p = PROPERTY_allowedParserOptions;
+ if (isInherited(p))
+ l.addAll(context.getAllowedParserOptions());
+ getRestOpAnnotations().stream()
+ .flatMap(ai ->
resolveCdl(ai.getStringArray(p).orElse(new String[0])))
+ .forEach(l::add);
+ return
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER,
removeNegations(l)));
+ }
+
+ /** Memoized effective allowed serializer option keys for this
operation. */
+ private final Memoizer<SortedSet<String>> allowedSerializerOptions =
memoizer(this::findAllowedSerializerOptions);
+
+ private SortedSet<String> findAllowedSerializerOptions() {
+ var l = new ArrayList<String>();
+ var p = PROPERTY_allowedSerializerOptions;
+ if (isInherited(p))
+ l.addAll(context.getAllowedSerializerOptions());
+ getRestOpAnnotations().stream()
+ .flatMap(ai ->
resolveCdl(ai.getStringArray(p).orElse(new String[0])))
+ .forEach(l::add);
+ return
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER,
removeNegations(l)));
+ }
+
+ private Stream<String> resolveCdl(String...values) {
+ if (values == null || values.length == 0)
+ return Stream.empty();
+ return Arrays.stream(values)
+ .filter(Objects::nonNull)
+ .map(s ->
RestOpContext.this.context.getVarResolver().resolve(s))
+ .map(StringUtils::split)
+ .flatMap(Collection::stream)
+ .map(String::trim)
+ .filter(StringUtils::isNotBlank);
+ }
+
+ /**
+ * Returns all {@link RestOp}-group annotations on this operation
method, in child-to-parent order.
+ *
+ * @return An unmodifiable list, never {@code null}.
+ */
+ public List<AnnotationInfo<?>> getRestOpAnnotations() {
+ return restOpAnnotations.get();
+ }
+
+ /**
+ * Returns {@code true} if context-level values for the given property
should be merged.
+ *
+ * @param property The annotation attribute name.
+ * @return {@code true} if {@code noInherit} does not contain this
property.
+ */
+ protected boolean isInherited(String property) {
+ return !noInheritOp.get().contains(property);
+ }
+
+ /**
+ * Returns the parser session-option keys allowed for this operation.
+ *
+ * @return An unmodifiable case-insensitive sorted set, never {@code
null}.
+ */
+ public SortedSet<String> getAllowedParserOptions() {
+ return allowedParserOptions.get();
+ }
+
+ /**
+ * Returns the serializer session-option keys allowed for this
operation.
+ *
+ * @return An unmodifiable case-insensitive sorted set, never {@code
null}.
+ */
+ public SortedSet<String> getAllowedSerializerOptions() {
+ return allowedSerializerOptions.get();
+ }
+
/**
* Context constructor.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 16feb2c4d5..05a9011442 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -18,6 +18,7 @@ package org.apache.juneau.rest;
import static java.util.Optional.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.rest.RestSharedConstants.*;
import static org.apache.juneau.commons.utils.IoUtils.*;
import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
@@ -31,6 +32,8 @@ import java.net.*;
import java.nio.charset.*;
import java.text.*;
import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
import org.apache.http.*;
import org.apache.http.message.*;
@@ -57,6 +60,9 @@ import org.apache.juneau.rest.staticfile.*;
import org.apache.juneau.rest.swagger.*;
import org.apache.juneau.rest.util.*;
import org.apache.juneau.svl.*;
+import org.apache.juneau.collections.*;
+import org.apache.juneau.marshaller.*;
+import org.apache.juneau.parser.ParseException;
import org.apache.juneau.uon.*;
import jakarta.servlet.*;
@@ -212,6 +218,8 @@ public class RestRequest extends HttpServletRequestWrapper {
private Swagger swagger;
private Charset charset;
+ private Map<String,Object> serializerSessionProperties;
+ private Map<String,Object> parserSessionProperties;
/**
* Constructor.
@@ -1427,6 +1435,200 @@ public class RestRequest extends
HttpServletRequestWrapper {
attrs.set(name, value);
}
+ /**
+ * Sets a serializer session property programmatically.
+ *
+ * <p>
+ * Session properties override context-level properties and can be used
to customize serializer behavior for this request only.
+ *
+ * @param name The property name (e.g. {@code "escapeSolidus"}).
+ * @param value The property value.
+ * @return This object.
+ */
+ public RestRequest setSerializerSessionProperty(String name, Object
value) {
+ if (serializerSessionProperties == null)
+ serializerSessionProperties = new LinkedHashMap<>();
+ serializerSessionProperties.put(name, value);
+ return this;
+ }
+
+ /**
+ * Sets a parser session property programmatically.
+ *
+ * <p>
+ * Session properties override context-level properties and can be used
to customize parser behavior for this request only.
+ *
+ * @param name The property name (e.g. {@code "trimStrings"}).
+ * @param value The property value.
+ * @return This object.
+ */
+ public RestRequest setParserSessionProperty(String name, Object value) {
+ if (parserSessionProperties == null)
+ parserSessionProperties = new LinkedHashMap<>();
+ parserSessionProperties.put(name, value);
+ return this;
+ }
+
+ /**
+ * Sets multiple serializer session properties programmatically.
+ *
+ * @param values The properties to set. Can be {@code null}.
+ * @return This object.
+ */
+ public RestRequest setSerializerSessionProperties(Map<String,Object>
values) {
+ if (values != null && !values.isEmpty()) {
+ if (serializerSessionProperties == null)
+ serializerSessionProperties = new
LinkedHashMap<>();
+ serializerSessionProperties.putAll(values);
+ }
+ return this;
+ }
+
+ /**
+ * Sets multiple parser session properties programmatically.
+ *
+ * @param values The properties to set. Can be {@code null}.
+ * @return This object.
+ */
+ public RestRequest setParserSessionProperties(Map<String,Object>
values) {
+ if (values != null && !values.isEmpty()) {
+ if (parserSessionProperties == null)
+ parserSessionProperties = new LinkedHashMap<>();
+ parserSessionProperties.putAll(values);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the merged serializer session property map for this request.
+ *
+ * <p>
+ * Merges (in order of increasing priority):
+ * <ol>
+ * <li>Request attributes (from {@link #getAttributes()}, e.g.
{@code defaultRequestAttributes})
+ * <li>UON-encoded {@code juneauSerializerOptions} query parameter
+ * <li>JSON5-encoded {@code X-Juneau-Serializer-Options} header
+ * <li>Programmatically-set properties via {@link
#setSerializerSessionProperty}
+ * </ol>
+ *
+ * <p>
+ * Client-supplied keys are validated against the allowlist from {@link
RestOpContext#getAllowedSerializerOptions()}.
+ * Keys not in the allowlist cause a {@code 400 Bad Request} response.
+ *
+ * @return An unmodifiable map of session properties; may be empty but
never {@code null}.
+ */
+ @SuppressWarnings({
+ "java:S3776" // cognitive complexity acceptable; sequential
null-checks for 4 sources
+ })
+ public Map<String,Object> getSerializerSessionPropertyMap() {
+ var allowlist = opContext.getAllowedSerializerOptions();
+ Map<String,Object> m1 = null, m2 = null;
+
+ var q =
getQueryParams().get(QUERY_juneauSerializerOptions).asString().orElse(null);
+ if (q != null && !q.isBlank()) {
+ m1 = parseUonMap(q, e -> badRequest(e, "Could not parse
UON session options from query parameter ''{0}''.",
QUERY_juneauSerializerOptions));
+ var invalid = m1.keySet().stream().filter(k ->
!allowlist.contains(k)).collect(Collectors.joining(","));
+ if (!invalid.isEmpty())
+ badRequest("Invalid session options in query
parameter ''{0}'': ''{1}''", QUERY_juneauSerializerOptions, invalid);
+ }
+
+ var h =
getHeaderParam(HEADER_JuneauSerializerOptions).asString().orElse(null);
+ if (h != null && !h.isBlank()) {
+ m2 = parseJsonMap(h, e -> badRequest(e, "Could not
parse JSON5 session options from header ''{0}''.",
HEADER_JuneauSerializerOptions));
+ var invalid = m2.keySet().stream().filter(k ->
!allowlist.contains(k)).collect(Collectors.joining(","));
+ if (!invalid.isEmpty())
+ badRequest("Invalid session options in header
''{0}'': ''{1}''", HEADER_JuneauSerializerOptions, invalid);
+ }
+
+ return mergeSessionMaps(getAttributes().asMap(), m1, m2,
serializerSessionProperties);
+ }
+
+ /**
+ * Returns the merged parser session property map for this request.
+ *
+ * <p>
+ * Merges (in order of increasing priority):
+ * <ol>
+ * <li>Request attributes (from {@link #getAttributes()}, e.g.
{@code defaultRequestAttributes})
+ * <li>UON-encoded {@code juneauParserOptions} query parameter
+ * <li>JSON5-encoded {@code X-Juneau-Parser-Options} header
+ * <li>Programmatically-set properties via {@link
#setParserSessionProperty}
+ * </ol>
+ *
+ * <p>
+ * Client-supplied keys are validated against the allowlist from {@link
RestOpContext#getAllowedParserOptions()}.
+ * Keys not in the allowlist cause a {@code 400 Bad Request} response.
+ *
+ * @return An unmodifiable map of session properties; may be empty but
never {@code null}.
+ */
+ @SuppressWarnings({
+ "java:S3776" // cognitive complexity acceptable; sequential
null-checks for 4 sources
+ })
+ public Map<String,Object> getParserSessionPropertyMap() {
+ var allowlist = opContext.getAllowedParserOptions();
+ Map<String,Object> m1 = null, m2 = null;
+
+ var q =
getQueryParams().get(QUERY_juneauParserOptions).asString().orElse(null);
+ if (q != null && !q.isBlank()) {
+ m1 = parseUonMap(q, e -> badRequest(e, "Could not parse
UON session options from query parameter ''{0}''.", QUERY_juneauParserOptions));
+ var invalid = m1.keySet().stream().filter(k ->
!allowlist.contains(k)).collect(Collectors.joining(","));
+ if (!invalid.isEmpty())
+ badRequest("Invalid session options in query
parameter ''{0}'': ''{1}''", QUERY_juneauParserOptions, invalid);
+ }
+
+ var h =
getHeaderParam(HEADER_JuneauParserOptions).asString().orElse(null);
+ if (h != null && !h.isBlank()) {
+ m2 = parseJsonMap(h, e -> badRequest(e, "Could not
parse JSON5 session options from header ''{0}''.", HEADER_JuneauParserOptions));
+ var invalid = m2.keySet().stream().filter(k ->
!allowlist.contains(k)).collect(Collectors.joining(","));
+ if (!invalid.isEmpty())
+ badRequest("Invalid session options in header
''{0}'': ''{1}''", HEADER_JuneauParserOptions, invalid);
+ }
+
+ return mergeSessionMaps(getAttributes().asMap(), m1, m2,
parserSessionProperties);
+ }
+
+ @SafeVarargs
+ private static Map<String,Object>
mergeSessionMaps(Map<String,Object>...maps) {
+ Map<String,Object> result = null;
+ for (var map : maps) {
+ if (map != null && !map.isEmpty()) {
+ if (result == null)
+ result = new LinkedHashMap<>(map);
+ else
+ result.putAll(map);
+ }
+ }
+ return result != null ? Collections.unmodifiableMap(result) :
Collections.emptyMap();
+ }
+
+ private static void badRequest(String msg, Object...args) {
+ throw new BadRequest(msg, args);
+ }
+
+ private static void badRequest(Exception causedBy, String msg,
Object...args) {
+ throw new BadRequest(causedBy, msg, args);
+ }
+
+ private static Map<String,Object> parseUonMap(String input,
Consumer<ParseException> onError) {
+ try {
+ var m = Uon.DEFAULT.read(input, JsonMap.class);
+ return m != null ? m : Collections.emptyMap();
+ } catch (ParseException e) {
+ onError.accept(e);
+ return Collections.emptyMap();
+ }
+ }
+
+ private static Map<String,Object> parseJsonMap(String input,
Consumer<ParseException> onError) {
+ try {
+ var m = Json5.DEFAULT.read(input, JsonMap.class);
+ return m != null ? m : Collections.emptyMap();
+ } catch (ParseException e) {
+ onError.accept(e);
+ return Collections.emptyMap();
+ }
+ }
+
/**
* Sets the charset to expect on the request content.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServerConstants.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServerConstants.java
new file mode 100644
index 0000000000..bd9d96afa6
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServerConstants.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+/**
+ * Static literals for {@code juneau-rest-server}: annotation attribute name
constants used for
+ * allowlist inheritance checks and other module-internal logic.
+ *
+ * <p>
+ * HTTP wire names shared with clients belong on {@link RestSharedConstants}
instead.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='jc'>{@link RestSharedConstants}
+ * </ul>
+ */
+@SuppressWarnings({
+ "java:S115", // PROPERTY_ + camelCase property name mirrors annotation
attribute name; not strict UPPER_SNAKE_CASE
+})
+public final class RestServerConstants {
+
+ private RestServerConstants() {}
+
+ /**
+ * The {@code "allowedParserOptions"} annotation attribute name — used
in {@code noInherit} matching.
+ *
+ * @see org.apache.juneau.rest.annotation.Rest#allowedParserOptions()
+ */
+ public static final String PROPERTY_allowedParserOptions =
"allowedParserOptions";
+
+ /**
+ * The {@code "allowedSerializerOptions"} annotation attribute name —
used in {@code noInherit} matching.
+ *
+ * @see
org.apache.juneau.rest.annotation.Rest#allowedSerializerOptions()
+ */
+ public static final String PROPERTY_allowedSerializerOptions =
"allowedSerializerOptions";
+
+ /** The {@code "noInherit"} annotation attribute name. */
+ public static final String PROPERTY_noInherit = "noInherit";
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
index 8b5419ce2a..7368140180 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
@@ -287,6 +287,62 @@ public @interface Rest {
*/
String[] consumes() default {};
+ /**
+ * Allowed serializer session option keys for this resource (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys (e.g.
<js>"escapeSolidus,maxIndent"</js>) that clients may
+ * send via the <js>"X-Juneau-Serializer-Options"</js> header or
<js>"juneauSerializerOptions"</js> query parameter.
+ * Keys not in the effective allowlist cause a {@code 400 Bad Request}
response.
+ *
+ * <p>
+ * Entries are merged in application order. A leading hyphen removes a
previously added key: <js>"-escapeSolidus"</js>.
+ * Method-level {@link
org.apache.juneau.rest.annotation.RestGet#allowedSerializerOptions()} values
are always merged on top.
+ * Use {@link #noInherit()} to prevent inheriting less-derived
contributions.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedSerializerOptions() default {};
+
+ /**
+ * Allowed parser session option keys for this resource (ordered merge,
prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Parser-Options"</js>
+ * header or <js>"juneauParserOptions"</js> query parameter. Parser
options are ignored for operations without
+ * a request-body parser. Keys not in the effective allowlist cause a
{@code 400 Bad Request} response.
+ *
+ * <p>
+ * Use {@link #noInherit()} to prevent inheriting less-derived
contributions.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedParserOptions() default {};
+
+ /**
+ * Property names for which less-derived contributions are NOT
inherited.
+ *
+ * <p>
+ * Accepted values: {@code "allowedSerializerOptions"}, {@code
"allowedParserOptions"}.
+ * Each entry is SVL-resolved then comma-split. Prevents the named
property from inheriting values from
+ * parent {@code @Rest} annotations (router hierarchy). The {@code
noInherit} attribute itself is never inherited.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] noInherit() default {};
+
/**
* Class-level response converters.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
index 9040454277..bacf7e3f99 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
@@ -108,6 +108,9 @@ public class RestAnnotation {
private String uriContext = "";
private String uriRelativity = "";
private String uriResolution = "";
+ private String[] allowedParserOptions = {};
+ private String[] allowedSerializerOptions = {};
+ private String[] noInherit = {};
private String[] consumes = {};
private String[] defaultRequestAttributes = {};
private String[] defaultRequestHeaders = {};
@@ -245,6 +248,39 @@ public class RestAnnotation {
return this;
}
+ /**
+ * Sets the {@link Rest#allowedSerializerOptions()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedSerializerOptions(String...value) {
+ allowedSerializerOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Rest#allowedParserOptions()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedParserOptions(String...value) {
+ allowedParserOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Rest#noInherit()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder noInherit(String...value) {
+ noInherit = value;
+ return this;
+ }
+
/**
* Sets the {@link Rest#converters()} property on this
annotation.
*
@@ -802,6 +838,9 @@ public class RestAnnotation {
private final String uriContext;
private final String uriRelativity;
private final String uriResolution;
+ private final String[] allowedParserOptions;
+ private final String[] allowedSerializerOptions;
+ private final String[] noInherit;
private final String[] consumes;
private final String[] produces;
private final String[] defaultRequestAttributes;
@@ -825,6 +864,9 @@ public class RestAnnotation {
children = copyOf(b.children);
clientVersionHeader = b.clientVersionHeader;
config = b.config;
+ allowedParserOptions = copyOf(b.allowedParserOptions);
+ allowedSerializerOptions =
copyOf(b.allowedSerializerOptions);
+ noInherit = copyOf(b.noInherit);
consumes = copyOf(b.consumes);
converters = copyOf(b.converters);
debug = b.debug;
@@ -907,6 +949,21 @@ public class RestAnnotation {
return config;
}
+ @Override /* Overridden from Rest */
+ public String[] allowedParserOptions() {
+ return allowedParserOptions;
+ }
+
+ @Override /* Overridden from Rest */
+ public String[] allowedSerializerOptions() {
+ return allowedSerializerOptions;
+ }
+
+ @Override /* Overridden from Rest */
+ public String[] noInherit() {
+ return noInherit;
+ }
+
@Override /* Overridden from Rest */
public String[] consumes() {
return consumes;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDelete.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDelete.java
index e279038bec..35d886bd34 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDelete.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDelete.java
@@ -452,6 +452,56 @@ public @interface RestDelete {
*/
String[] path() default {};
+ /**
+ * Allowed serializer session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Serializer-Options"</js>
+ * header or <js>"juneauSerializerOptions"</js> query parameter.
+ * Merged on top of resource-level {@link
Rest#allowedSerializerOptions()} values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedSerializerOptions() default {};
+
+ /**
+ * Allowed parser session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Parser-Options"</js>
+ * header or <js>"juneauParserOptions"</js> query parameter.
+ * Merged on top of resource-level {@link Rest#allowedParserOptions()}
values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedParserOptions() default {};
+
+ /**
+ * Property names for which less-derived contributions are NOT
inherited.
+ *
+ * <p>
+ * Accepted values: {@code "allowedSerializerOptions"}, {@code
"allowedParserOptions"}.
+ * Prevents the named property from inheriting values from the
enclosing {@code @Rest} annotation.
+ * The {@code noInherit} attribute itself is never inherited.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] noInherit() default {};
+
/**
* Role guard.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
index 7dffbb0001..fa9e2b6d60 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
@@ -77,6 +77,9 @@ public class RestDeleteAnnotation {
private String[] defaultRequestAttributes = {};
private String[] defaultRequestHeaders = {};
private String[] defaultResponseHeaders = {};
+ private String[] allowedParserOptions = {};
+ private String[] allowedSerializerOptions = {};
+ private String[] noInherit = {};
private String[] path = {};
/**
@@ -241,6 +244,39 @@ public class RestDeleteAnnotation {
return this;
}
+ /**
+ * Sets the {@link RestDelete#allowedSerializerOptions()}
property on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedSerializerOptions(String...value) {
+ allowedSerializerOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestDelete#allowedParserOptions()} property
on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedParserOptions(String...value) {
+ allowedParserOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestDelete#noInherit()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder noInherit(String...value) {
+ noInherit = value;
+ return this;
+ }
+
/**
* Sets the {@link RestDelete#roleGuard()} property on this
annotation.
*
@@ -374,6 +410,9 @@ public class RestDeleteAnnotation {
private final String[] defaultRequestAttributes;
private final String[] defaultRequestHeaders;
private final String[] defaultResponseHeaders;
+ private final String[] allowedParserOptions;
+ private final String[] allowedSerializerOptions;
+ private final String[] noInherit;
private final String[] path;
Object(RestDeleteAnnotation.Builder b) {
@@ -390,6 +429,9 @@ public class RestDeleteAnnotation {
encoders = copyOf(b.encoders);
guards = copyOf(b.guards);
matchers = copyOf(b.matchers);
+ allowedParserOptions = copyOf(b.allowedParserOptions);
+ allowedSerializerOptions =
copyOf(b.allowedSerializerOptions);
+ noInherit = copyOf(b.noInherit);
path = copyOf(b.path);
roleGuard = b.roleGuard;
rolesDeclared = b.rolesDeclared;
@@ -453,6 +495,21 @@ public class RestDeleteAnnotation {
return matchers;
}
+ @Override /* Overridden from RestDelete */
+ public String[] allowedParserOptions() {
+ return allowedParserOptions;
+ }
+
+ @Override /* Overridden from RestDelete */
+ public String[] allowedSerializerOptions() {
+ return allowedSerializerOptions;
+ }
+
+ @Override /* Overridden from RestDelete */
+ public String[] noInherit() {
+ return noInherit;
+ }
+
@Override /* Overridden from RestDelete */
public String[] path() {
return path;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGet.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGet.java
index 3e7fd50769..7ec04ef758 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGet.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGet.java
@@ -493,6 +493,56 @@ public @interface RestGet {
*/
String[] produces() default {};
+ /**
+ * Allowed serializer session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Serializer-Options"</js>
+ * header or <js>"juneauSerializerOptions"</js> query parameter.
+ * Merged on top of resource-level {@link
Rest#allowedSerializerOptions()} values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedSerializerOptions() default {};
+
+ /**
+ * Allowed parser session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Parser-Options"</js>
+ * header or <js>"juneauParserOptions"</js> query parameter.
+ * Merged on top of resource-level {@link Rest#allowedParserOptions()}
values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedParserOptions() default {};
+
+ /**
+ * Property names for which less-derived contributions are NOT
inherited.
+ *
+ * <p>
+ * Accepted values: {@code "allowedSerializerOptions"}, {@code
"allowedParserOptions"}.
+ * Prevents the named property from inheriting values from the
enclosing {@code @Rest} annotation.
+ * The {@code noInherit} attribute itself is never inherited.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] noInherit() default {};
+
/**
* Role guard.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
index 7ec55bf2cc..b3ab32cded 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
@@ -81,6 +81,9 @@ public class RestGetAnnotation {
private String[] defaultRequestAttributes = {};
private String[] defaultRequestHeaders = {};
private String[] defaultResponseHeaders = {};
+ private String[] allowedParserOptions = {};
+ private String[] allowedSerializerOptions = {};
+ private String[] noInherit = {};
private String[] path = {};
private String[] produces = {};
@@ -269,6 +272,39 @@ public class RestGetAnnotation {
return this;
}
+ /**
+ * Sets the {@link RestGet#allowedSerializerOptions()} property
on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedSerializerOptions(String...value) {
+ allowedSerializerOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestGet#allowedParserOptions()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedParserOptions(String...value) {
+ allowedParserOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestGet#noInherit()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder noInherit(String...value) {
+ noInherit = value;
+ return this;
+ }
+
/**
* Sets the {@link RestGet#roleGuard()} property on this
annotation.
*
@@ -419,6 +455,9 @@ public class RestGetAnnotation {
private final String[] defaultRequestAttributes;
private final String[] defaultRequestHeaders;
private final String[] defaultResponseHeaders;
+ private final String[] allowedParserOptions;
+ private final String[] allowedSerializerOptions;
+ private final String[] noInherit;
private final String[] path;
private final String[] produces;
@@ -437,6 +476,9 @@ public class RestGetAnnotation {
encoders = copyOf(b.encoders);
guards = copyOf(b.guards);
matchers = copyOf(b.matchers);
+ allowedParserOptions = copyOf(b.allowedParserOptions);
+ allowedSerializerOptions =
copyOf(b.allowedSerializerOptions);
+ noInherit = copyOf(b.noInherit);
path = copyOf(b.path);
produces = copyOf(b.produces);
roleGuard = b.roleGuard;
@@ -507,6 +549,21 @@ public class RestGetAnnotation {
return matchers;
}
+ @Override /* Overridden from RestGet */
+ public String[] allowedParserOptions() {
+ return allowedParserOptions;
+ }
+
+ @Override /* Overridden from RestGet */
+ public String[] allowedSerializerOptions() {
+ return allowedSerializerOptions;
+ }
+
+ @Override /* Overridden from RestGet */
+ public String[] noInherit() {
+ return noInherit;
+ }
+
@Override /* Overridden from RestGet */
public String[] path() {
return path;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
index 3d1b0d0fa6..83f64572bf 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
@@ -726,6 +726,56 @@ public @interface RestOp {
*/
String[] produces() default {};
+ /**
+ * Allowed serializer session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Serializer-Options"</js>
+ * header or <js>"juneauSerializerOptions"</js> query parameter.
+ * Merged on top of resource-level {@link
Rest#allowedSerializerOptions()} values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedSerializerOptions() default {};
+
+ /**
+ * Allowed parser session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Parser-Options"</js>
+ * header or <js>"juneauParserOptions"</js> query parameter.
+ * Merged on top of resource-level {@link Rest#allowedParserOptions()}
values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedParserOptions() default {};
+
+ /**
+ * Property names for which less-derived contributions are NOT
inherited.
+ *
+ * <p>
+ * Accepted values: {@code "allowedSerializerOptions"}, {@code
"allowedParserOptions"}.
+ * Prevents the named property from inheriting values from the
enclosing {@code @Rest} annotation.
+ * The {@code noInherit} attribute itself is never inherited.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] noInherit() default {};
+
/**
* Role guard.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
index 1ebfe22ae4..b6ee11ddf8 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
@@ -92,6 +92,9 @@ public class RestOpAnnotation {
private String[] defaultResponseHeaders = {};
private String[] path = {};
private String[] produces = {};
+ private String[] allowedSerializerOptions = {};
+ private String[] allowedParserOptions = {};
+ private String[] noInherit = {};
/**
* Constructor.
@@ -344,6 +347,39 @@ public class RestOpAnnotation {
return this;
}
+ /**
+ * Sets the {@link RestOp#allowedSerializerOptions()} property
on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedSerializerOptions(String...value) {
+ allowedSerializerOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOp#allowedParserOptions()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedParserOptions(String...value) {
+ allowedParserOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOp#noInherit()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder noInherit(String...value) {
+ noInherit = value;
+ return this;
+ }
+
/**
* Sets the {@link RestOp#roleGuard()} property on this
annotation.
*
@@ -517,6 +553,9 @@ public class RestOpAnnotation {
private final String[] defaultResponseHeaders;
private final String[] path;
private final String[] produces;
+ private final String[] allowedSerializerOptions;
+ private final String[] allowedParserOptions;
+ private final String[] noInherit;
Object(RestOpAnnotation.Builder b) {
super(b);
@@ -541,6 +580,9 @@ public class RestOpAnnotation {
parsers = copyOf(b.parsers);
path = copyOf(b.path);
produces = copyOf(b.produces);
+ allowedSerializerOptions =
copyOf(b.allowedSerializerOptions);
+ allowedParserOptions = copyOf(b.allowedParserOptions);
+ noInherit = copyOf(b.noInherit);
roleGuard = b.roleGuard;
rolesDeclared = b.rolesDeclared;
serializers = copyOf(b.serializers);
@@ -649,6 +691,21 @@ public class RestOpAnnotation {
return produces;
}
+ @Override /* Overridden from RestOp */
+ public String[] allowedSerializerOptions() {
+ return allowedSerializerOptions;
+ }
+
+ @Override /* Overridden from RestOp */
+ public String[] allowedParserOptions() {
+ return allowedParserOptions;
+ }
+
+ @Override /* Overridden from RestOp */
+ public String[] noInherit() {
+ return noInherit;
+ }
+
@Override /* Overridden from RestOp */
public String roleGuard() {
return roleGuard;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptions.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptions.java
index 789c500487..f6746461a8 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptions.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptions.java
@@ -493,6 +493,56 @@ public @interface RestOptions {
*/
String[] produces() default {};
+ /**
+ * Allowed serializer session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Serializer-Options"</js>
+ * header or <js>"juneauSerializerOptions"</js> query parameter.
+ * Merged on top of resource-level {@link
Rest#allowedSerializerOptions()} values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedSerializerOptions() default {};
+
+ /**
+ * Allowed parser session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Parser-Options"</js>
+ * header or <js>"juneauParserOptions"</js> query parameter.
+ * Merged on top of resource-level {@link Rest#allowedParserOptions()}
values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedParserOptions() default {};
+
+ /**
+ * Property names for which less-derived contributions are NOT
inherited.
+ *
+ * <p>
+ * Accepted values: {@code "allowedSerializerOptions"}, {@code
"allowedParserOptions"}.
+ * Prevents the named property from inheriting values from the
enclosing {@code @Rest} annotation.
+ * The {@code noInherit} attribute itself is never inherited.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] noInherit() default {};
+
/**
* Role guard.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
index a66d111333..3df2f33cbd 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
@@ -81,6 +81,9 @@ public class RestOptionsAnnotation {
private String[] defaultRequestAttributes = {};
private String[] defaultRequestHeaders = {};
private String[] defaultResponseHeaders = {};
+ private String[] allowedParserOptions = {};
+ private String[] allowedSerializerOptions = {};
+ private String[] noInherit = {};
private String[] path = {};
private String[] produces = {};
@@ -269,6 +272,39 @@ public class RestOptionsAnnotation {
return this;
}
+ /**
+ * Sets the {@link RestOptions#allowedSerializerOptions()}
property on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedSerializerOptions(String...value) {
+ allowedSerializerOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#allowedParserOptions()} property
on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedParserOptions(String...value) {
+ allowedParserOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#noInherit()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder noInherit(String...value) {
+ noInherit = value;
+ return this;
+ }
+
/**
* Sets the {@link RestOptions#roleGuard()} property on this
annotation.
*
@@ -419,6 +455,9 @@ public class RestOptionsAnnotation {
private final String[] defaultRequestAttributes;
private final String[] defaultRequestHeaders;
private final String[] defaultResponseHeaders;
+ private final String[] allowedParserOptions;
+ private final String[] allowedSerializerOptions;
+ private final String[] noInherit;
private final String[] path;
private final String[] produces;
@@ -437,6 +476,9 @@ public class RestOptionsAnnotation {
encoders = copyOf(b.encoders);
guards = copyOf(b.guards);
matchers = copyOf(b.matchers);
+ allowedParserOptions = copyOf(b.allowedParserOptions);
+ allowedSerializerOptions =
copyOf(b.allowedSerializerOptions);
+ noInherit = copyOf(b.noInherit);
path = copyOf(b.path);
produces = copyOf(b.produces);
roleGuard = b.roleGuard;
@@ -507,6 +549,21 @@ public class RestOptionsAnnotation {
return matchers;
}
+ @Override /* Overridden from RestOptions */
+ public String[] allowedParserOptions() {
+ return allowedParserOptions;
+ }
+
+ @Override /* Overridden from RestOptions */
+ public String[] allowedSerializerOptions() {
+ return allowedSerializerOptions;
+ }
+
+ @Override /* Overridden from RestOptions */
+ public String[] noInherit() {
+ return noInherit;
+ }
+
@Override /* Overridden from RestOptions */
public String[] path() {
return path;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatch.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatch.java
index 1d3e45ec71..d2c325dd6c 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatch.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatch.java
@@ -649,6 +649,56 @@ public @interface RestPatch {
*/
String[] produces() default {};
+ /**
+ * Allowed serializer session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Serializer-Options"</js>
+ * header or <js>"juneauSerializerOptions"</js> query parameter.
+ * Merged on top of resource-level {@link
Rest#allowedSerializerOptions()} values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedSerializerOptions() default {};
+
+ /**
+ * Allowed parser session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Parser-Options"</js>
+ * header or <js>"juneauParserOptions"</js> query parameter.
+ * Merged on top of resource-level {@link Rest#allowedParserOptions()}
values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedParserOptions() default {};
+
+ /**
+ * Property names for which less-derived contributions are NOT
inherited.
+ *
+ * <p>
+ * Accepted values: {@code "allowedSerializerOptions"}, {@code
"allowedParserOptions"}.
+ * Prevents the named property from inheriting values from the
enclosing {@code @Rest} annotation.
+ * The {@code noInherit} attribute itself is never inherited.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] noInherit() default {};
+
/**
* Role guard.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
index 00b1d1f060..a444822bc8 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
@@ -86,6 +86,9 @@ public class RestPatchAnnotation {
private String[] defaultRequestHeaders = {};
private String[] defaultResponseHeaders = {};
private String[] description = {};
+ private String[] allowedParserOptions = {};
+ private String[] allowedSerializerOptions = {};
+ private String[] noInherit = {};
private String[] path = {};
private String[] produces = {};
@@ -329,6 +332,39 @@ public class RestPatchAnnotation {
return this;
}
+ /**
+ * Sets the {@link RestPatch#allowedSerializerOptions()}
property on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedSerializerOptions(String...value) {
+ allowedSerializerOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestPatch#allowedParserOptions()} property
on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedParserOptions(String...value) {
+ allowedParserOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestPatch#noInherit()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder noInherit(String...value) {
+ noInherit = value;
+ return this;
+ }
+
/**
* Sets the {@link RestPatch#roleGuard()} property on this
annotation.
*
@@ -489,6 +525,9 @@ public class RestPatchAnnotation {
private final String[] defaultRequestAttributes;
private final String[] defaultRequestHeaders;
private final String[] defaultResponseHeaders;
+ private final String[] allowedParserOptions;
+ private final String[] allowedSerializerOptions;
+ private final String[] noInherit;
private final String[] path;
private final String[] produces;
@@ -512,6 +551,9 @@ public class RestPatchAnnotation {
matchers = copyOf(b.matchers);
maxInput = b.maxInput;
parsers = copyOf(b.parsers);
+ allowedParserOptions = copyOf(b.allowedParserOptions);
+ allowedSerializerOptions =
copyOf(b.allowedSerializerOptions);
+ noInherit = copyOf(b.noInherit);
path = copyOf(b.path);
produces = copyOf(b.produces);
roleGuard = b.roleGuard;
@@ -607,6 +649,21 @@ public class RestPatchAnnotation {
return parsers;
}
+ @Override /* Overridden from RestPatch */
+ public String[] allowedParserOptions() {
+ return allowedParserOptions;
+ }
+
+ @Override /* Overridden from RestPatch */
+ public String[] allowedSerializerOptions() {
+ return allowedSerializerOptions;
+ }
+
+ @Override /* Overridden from RestPatch */
+ public String[] noInherit() {
+ return noInherit;
+ }
+
@Override /* Overridden from RestPatch */
public String[] path() {
return path;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPost.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPost.java
index e4189c1910..2e40b26efc 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPost.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPost.java
@@ -649,6 +649,56 @@ public @interface RestPost {
*/
String[] produces() default {};
+ /**
+ * Allowed serializer session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Serializer-Options"</js>
+ * header or <js>"juneauSerializerOptions"</js> query parameter.
+ * Merged on top of resource-level {@link
Rest#allowedSerializerOptions()} values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedSerializerOptions() default {};
+
+ /**
+ * Allowed parser session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Parser-Options"</js>
+ * header or <js>"juneauParserOptions"</js> query parameter.
+ * Merged on top of resource-level {@link Rest#allowedParserOptions()}
values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedParserOptions() default {};
+
+ /**
+ * Property names for which less-derived contributions are NOT
inherited.
+ *
+ * <p>
+ * Accepted values: {@code "allowedSerializerOptions"}, {@code
"allowedParserOptions"}.
+ * Prevents the named property from inheriting values from the
enclosing {@code @Rest} annotation.
+ * The {@code noInherit} attribute itself is never inherited.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] noInherit() default {};
+
/**
* Role guard.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
index 0623c6e7bf..b415dbcf6b 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
@@ -86,6 +86,9 @@ public class RestPostAnnotation {
private String[] defaultRequestAttributes = {};
private String[] defaultRequestHeaders = {};
private String[] defaultResponseHeaders = {};
+ private String[] allowedParserOptions = {};
+ private String[] allowedSerializerOptions = {};
+ private String[] noInherit = {};
private String[] path = {};
private String[] produces = {};
@@ -329,6 +332,39 @@ public class RestPostAnnotation {
return this;
}
+ /**
+ * Sets the {@link RestPost#allowedSerializerOptions()}
property on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedSerializerOptions(String...value) {
+ allowedSerializerOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestPost#allowedParserOptions()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedParserOptions(String...value) {
+ allowedParserOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestPost#noInherit()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder noInherit(String...value) {
+ noInherit = value;
+ return this;
+ }
+
/**
* Sets the {@link RestPost#roleGuard()} property on this
annotation.
*
@@ -489,6 +525,9 @@ public class RestPostAnnotation {
private final String[] defaultRequestAttributes;
private final String[] defaultRequestHeaders;
private final String[] defaultResponseHeaders;
+ private final String[] allowedParserOptions;
+ private final String[] allowedSerializerOptions;
+ private final String[] noInherit;
private final String[] path;
private final String[] produces;
@@ -512,6 +551,9 @@ public class RestPostAnnotation {
matchers = copyOf(b.matchers);
maxInput = b.maxInput;
parsers = copyOf(b.parsers);
+ allowedParserOptions = copyOf(b.allowedParserOptions);
+ allowedSerializerOptions =
copyOf(b.allowedSerializerOptions);
+ noInherit = copyOf(b.noInherit);
path = copyOf(b.path);
produces = copyOf(b.produces);
roleGuard = b.roleGuard;
@@ -607,6 +649,21 @@ public class RestPostAnnotation {
return parsers;
}
+ @Override /* Overridden from RestPost */
+ public String[] allowedParserOptions() {
+ return allowedParserOptions;
+ }
+
+ @Override /* Overridden from RestPost */
+ public String[] allowedSerializerOptions() {
+ return allowedSerializerOptions;
+ }
+
+ @Override /* Overridden from RestPost */
+ public String[] noInherit() {
+ return noInherit;
+ }
+
@Override /* Overridden from RestPost */
public String[] path() {
return path;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPut.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPut.java
index 5ab32819a5..c34f2713e0 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPut.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPut.java
@@ -649,6 +649,56 @@ public @interface RestPut {
*/
String[] produces() default {};
+ /**
+ * Allowed serializer session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Serializer-Options"</js>
+ * header or <js>"juneauSerializerOptions"</js> query parameter.
+ * Merged on top of resource-level {@link
Rest#allowedSerializerOptions()} values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedSerializerOptions() default {};
+
+ /**
+ * Allowed parser session option keys for this operation (ordered
merge, prefix {@code -key} removes a key).
+ *
+ * <p>
+ * Comma-delimited list of session property keys that clients may send
via the <js>"X-Juneau-Parser-Options"</js>
+ * header or <js>"juneauParserOptions"</js> query parameter.
+ * Merged on top of resource-level {@link Rest#allowedParserOptions()}
values.
+ * Use {@link #noInherit()} to replace rather than extend the
resource-level list.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session
Options - Safe Properties</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] allowedParserOptions() default {};
+
+ /**
+ * Property names for which less-derived contributions are NOT
inherited.
+ *
+ * <p>
+ * Accepted values: {@code "allowedSerializerOptions"}, {@code
"allowedParserOptions"}.
+ * Prevents the named property from inheriting values from the
enclosing {@code @Rest} annotation.
+ * The {@code noInherit} attribute itself is never inherited.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] noInherit() default {};
+
/**
* Role guard.
*
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
index dbaff9eb03..c6b41c7b58 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
@@ -86,6 +86,9 @@ public class RestPutAnnotation {
private String[] defaultRequestAttributes = {};
private String[] defaultRequestHeaders = {};
private String[] defaultResponseHeaders = {};
+ private String[] allowedParserOptions = {};
+ private String[] allowedSerializerOptions = {};
+ private String[] noInherit = {};
private String[] path = {};
private String[] produces = {};
@@ -329,6 +332,39 @@ public class RestPutAnnotation {
return this;
}
+ /**
+ * Sets the {@link RestPut#allowedSerializerOptions()} property
on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedSerializerOptions(String...value) {
+ allowedSerializerOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestPut#allowedParserOptions()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder allowedParserOptions(String...value) {
+ allowedParserOptions = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestPut#noInherit()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder noInherit(String...value) {
+ noInherit = value;
+ return this;
+ }
+
/**
* Sets the {@link RestPut#roleGuard()} property on this
annotation.
*
@@ -489,6 +525,9 @@ public class RestPutAnnotation {
private final String[] defaultRequestAttributes;
private final String[] defaultRequestHeaders;
private final String[] defaultResponseHeaders;
+ private final String[] allowedParserOptions;
+ private final String[] allowedSerializerOptions;
+ private final String[] noInherit;
private final String[] path;
private final String[] produces;
@@ -512,6 +551,9 @@ public class RestPutAnnotation {
matchers = copyOf(b.matchers);
maxInput = b.maxInput;
parsers = copyOf(b.parsers);
+ allowedParserOptions = copyOf(b.allowedParserOptions);
+ allowedSerializerOptions =
copyOf(b.allowedSerializerOptions);
+ noInherit = copyOf(b.noInherit);
path = copyOf(b.path);
produces = copyOf(b.produces);
roleGuard = b.roleGuard;
@@ -607,6 +649,21 @@ public class RestPutAnnotation {
return parsers;
}
+ @Override /* Overridden from RestPut */
+ public String[] allowedParserOptions() {
+ return allowedParserOptions;
+ }
+
+ @Override /* Overridden from RestPut */
+ public String[] allowedSerializerOptions() {
+ return allowedSerializerOptions;
+ }
+
+ @Override /* Overridden from RestPut */
+ public String[] noInherit() {
+ return noInherit;
+ }
+
@Override /* Overridden from RestPut */
public String[] path() {
return path;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
index 5a98c49109..8b97435d97 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
@@ -196,6 +196,47 @@ import org.apache.juneau.xml.*;
ParquetParser.class,
ProtoParser.class,
MarkdownParser.class
+ },
+
+ // Serializer session properties that clients are allowed to override
via HTTP headers/query parameters.
+ allowedSerializerOptions={
+ "useWhitespace", // WriterSerializer - enable/disable
whitespace
+ "maxIndent", // WriterSerializer - max indentation
level
+ "quoteChar", // WriterSerializer - quote character
+ "keepNullProperties", // SerializerSession - include null
properties
+ "trimStrings", // SerializerSession - trim string
values
+ "addBeanTypes", // SerializerSession - add bean type
annotations
+ "addRootType", // SerializerSession - add root type
annotation
+ "sortCollections", // SerializerSession - sort
collections
+ "sortMaps", // SerializerSession - sort maps
+ "trimEmptyCollections", // SerializerSession - trim empty
collections
+ "trimEmptyMaps", // SerializerSession - trim empty maps
+ "binaryFormat", // OutputStreamSerializer - binary
encoding format
+ "escapeSolidus", // JsonSerializerSession - escape
forward slashes
+ "encoding", // UonSerializerSession - URL-encode
characters
+ "paramFormat", // UonSerializerSession - parameter
format style
+ "addNamespaceUrisToRoot", // XmlSerializerSession - add
namespace URIs to root
+ "autoDetectNamespaces", // XmlSerializerSession - auto-detect
namespaces
+ "enableNamespaces", // XmlSerializerSession - enable
namespace support
+ "textNodeDelimiter", // XmlSerializerSession - text node
delimiter
+ "addKeyValueTableHeaders",// HtmlSerializerSession - add
key/value table headers
+ "detectLabelParameters", // HtmlSerializerSession - detect
label parameters
+ "detectLinksInStrings", // HtmlSerializerSession - detect
links in strings
+ "labelParameter", // HtmlSerializerSession - label
parameter name
+ "uriAnchorText", // HtmlSerializerSession - URI anchor
text strategy
+ "byteArrayFormat", // CsvSerializerSession - byte array
encoding format
+ "allowNestedStructures" // CsvSerializerSession - allow
nested structures
+ },
+
+ // Parser session properties that clients are allowed to override via
HTTP headers/query parameters.
+ allowedParserOptions={
+ "trimStrings", // ParserSession - trim string values
+ "binaryFormat", // InputStreamParser - binary
decoding format
+ "validateEnd", // JsonParserSession/UonParserSession
- validate end of input
+ "decoding", // UonParserSession - URL-decode
characters
+ "expandedParams", // UrlEncodingParserSession -
expanded parameters
+ "preserveRootElement", // XmlParserSession - preserve root
element
+ "validating" // XmlParserSession - validate XML
}
)
public interface BasicUniversalConfig extends DefaultConfig, DefaultHtmlConfig
{}
\ No newline at end of file
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestContent.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestContent.java
index 2b95e4c0e0..1de14c5d2f 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestContent.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestContent.java
@@ -551,7 +551,7 @@ public class RequestContent {
// @formatter:off
var session = p
.createSession()
- .properties(req.getAttributes().asMap())
+ .properties(req.getParserSessionPropertyMap())
.javaMethod(req.getOpContext().getJavaMethod())
.locale(locale)
.timeZone(timeZone.orElse(null))
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/SerializedPojoProcessor.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/SerializedPojoProcessor.java
index a00bf9a02f..e10b0eb419 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/SerializedPojoProcessor.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/SerializedPojoProcessor.java
@@ -72,7 +72,7 @@ public class SerializedPojoProcessor implements
ResponseProcessor {
// @formatter:off
SerializerSession session = s
.createSession()
- .properties(req.getAttributes().asMap())
+
.properties(req.getSerializerSessionPropertyMap())
.javaMethod(req.getOpContext().getJavaMethod())
.locale(req.getLocale())
.timeZone(req.getTimeZone().orElse(null))
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/CollectionUtils_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/CollectionUtils_Test.java
index b3e1234657..45c999ae3f 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/CollectionUtils_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/CollectionUtils_Test.java
@@ -1659,4 +1659,26 @@ class CollectionUtils_Test extends TestBase {
assertEquals(1, intSet.first());
assertEquals(3, intSet.last());
}
+
+
//====================================================================================================
+ // isEmpty(Collection<?>)
+
//====================================================================================================
+ @Test
+ void a096_isEmpty_collection() {
+ assertTrue(isEmpty((java.util.Collection<?>)null));
+ assertTrue(isEmpty(list()));
+ assertFalse(isEmpty(list("a")));
+ assertFalse(isEmpty(list("a", "b")));
+ }
+
+
//====================================================================================================
+ // isEmpty(Map<?,?>)
+
//====================================================================================================
+ @Test
+ void a097_isEmpty_map() {
+ assertTrue(isEmpty((java.util.Map<?,?>)null));
+ assertTrue(isEmpty(map()));
+ assertFalse(isEmpty(map("k", "v")));
+ assertFalse(isEmpty(map("k1", "v1", "k2", "v2")));
+ }
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java
new file mode 100644
index 0000000000..1aa0c2d4f8
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.TestBase;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.BasicRestObject;
+import org.junit.jupiter.api.*;
+
+class NoInherit_Test extends TestBase {
+
+ private static RestContext restContext(Class<? extends BasicRestObject>
c) throws Exception {
+ var o = c.getDeclaredConstructor().newInstance();
+
+ // Check if class has a parent REST resource (not
BasicRestObject)
+ RestContext parentContext = null;
+ var superClass = c.getSuperclass();
+ if (superClass != null && superClass != BasicRestObject.class
&& BasicRestObject.class.isAssignableFrom(superClass)) {
+ @SuppressWarnings("unchecked")
+ var parentClass = (Class<? extends BasicRestObject>)
superClass;
+ parentContext = restContext(parentClass);
+ }
+
+ return RestContext.create(c, parentContext, null).init(() ->
o).build().postInit().postInitChildFirst();
+ }
+
+ @Rest(allowedSerializerOptions = "parentSer")
+ public static class ParentSer extends BasicRestObject {}
+
+ @Rest(allowedSerializerOptions = "childSer", noInherit =
"allowedSerializerOptions")
+ public static class ChildSer extends ParentSer {}
+
+ @Test
+ void a01_classNoInherit_skipsParentSerializerAllowlist() throws
Exception {
+ var ctx = restContext(ChildSer.class);
+ var keys = ctx.getAllowedSerializerOptions();
+
+ assertTrue(keys.contains("childSer"));
+ assertFalse(keys.contains("parentSer"));
+ }
+
+ @Rest(allowedSerializerOptions = "pBoth")
+ public static class ParentBoth extends BasicRestObject {}
+
+ @Rest(allowedSerializerOptions = "cBoth")
+ public static class ChildBoth extends ParentBoth {}
+
+ @Test
+ void a02_withoutNoInherit_mergesParentSerializerAllowlist() throws
Exception {
+ var ctx = restContext(ChildBoth.class);
+ var keys = ctx.getAllowedSerializerOptions();
+
+ assertTrue(keys.contains("pBoth"));
+ assertTrue(keys.contains("cBoth"));
+ }
+
+ public static class ParentM extends BasicRestObject {
+ @RestGet(allowedSerializerOptions = "parentM")
+ public void get() {
+ // Intentionally empty - method only used for
annotation metadata testing
+ }
+ }
+
+ public static class ChildM extends ParentM {
+ @RestGet(allowedSerializerOptions = "childM", noInherit =
"allowedSerializerOptions")
+ @Override
+ public void get() {
+ // Intentionally empty - method only used for
annotation metadata testing
+ }
+ }
+
+ @Test
+ void a03_methodNoInherit_stillInheritsParentMethodSerializerAllowlist()
throws Exception {
+ var ctx = restContext(ChildM.class);
+ var op = ctx.getRestOperations().getOpContexts().stream()
+ .filter(o ->
ChildM.class.equals(o.getJavaMethod().getDeclaringClass()) &&
"get".equals(o.getJavaMethod().getName()))
+ .findFirst()
+ .orElseThrow();
+ var keys = op.getAllowedSerializerOptions();
+
+ // Method annotations ALWAYS inherit from parent methods,
regardless of noInherit
+ // noInherit only blocks inheritance from the REST class context
+ assertTrue(keys.contains("childM"));
+ assertTrue(keys.contains("parentM"));
+ }
+
+ // Test case for aggregated noInherit: parent has noInherit="prop1",
child has noInherit="prop2"
+ // The aggregated noInherit should be {"prop1", "prop2"}, but
allowedSerializerOptions should
+ // include values from both parent and child since neither blocks
allowedSerializerOptions
+ public static class ParentAggregated extends BasicRestObject {
+ @RestGet(allowedSerializerOptions = "parentOpt", noInherit =
"prop1")
+ public void get() {
+ // Intentionally empty - method only used for
annotation metadata testing
+ }
+ }
+
+ public static class ChildAggregated extends ParentAggregated {
+ @RestGet(allowedSerializerOptions = "childOpt", noInherit =
"prop2")
+ @Override
+ public void get() {
+ // Intentionally empty - method only used for
annotation metadata testing
+ }
+ }
+
+ @Test
+ void a04_aggregatedNoInherit_includesBothParentAndChild() throws
Exception {
+ var ctx = restContext(ChildAggregated.class);
+ var op = ctx.getRestOperations().getOpContexts().stream()
+ .filter(o ->
ChildAggregated.class.equals(o.getJavaMethod().getDeclaringClass()) &&
"get".equals(o.getJavaMethod().getName()))
+ .findFirst()
+ .orElseThrow();
+ var keys = op.getAllowedSerializerOptions();
+
+ // Both parent and child options should be present since
neither has allowedSerializerOptions in noInherit
+ assertTrue(keys.contains("childOpt"));
+ assertTrue(keys.contains("parentOpt"));
+ }
+
+ // Test that noInherit blocks REST class context inheritance but NOT
parent method inheritance
+ @Rest(allowedSerializerOptions = "classLevel")
+ public static class ParentWithClassLevel extends BasicRestObject {
+ @RestGet(allowedSerializerOptions = "parentMethod")
+ public void get() {
+ // Intentionally empty - method only used for
annotation metadata testing
+ }
+ }
+
+ public static class ChildBlocksClassInheritance extends
ParentWithClassLevel {
+ @RestGet(allowedSerializerOptions = "childMethod", noInherit =
"allowedSerializerOptions")
+ @Override
+ public void get() {
+ // Intentionally empty - method only used for
annotation metadata testing
+ }
+ }
+
+ @Test
+ void a05_methodNoInherit_blocksClassLevelButNotParentMethod() throws
Exception {
+ var ctx = restContext(ChildBlocksClassInheritance.class);
+ var op = ctx.getRestOperations().getOpContexts().stream()
+ .filter(o ->
ChildBlocksClassInheritance.class.equals(o.getJavaMethod().getDeclaringClass())
&& "get".equals(o.getJavaMethod().getName()))
+ .findFirst()
+ .orElseThrow();
+ var keys = op.getAllowedSerializerOptions();
+
+ // Should include method-level keys from both child and parent
methods
+ assertTrue(keys.contains("childMethod"));
+ assertTrue(keys.contains("parentMethod"));
+
+ // Should NOT include class-level key because of noInherit
+ assertFalse(keys.contains("classLevel"));
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestRequest_SessionProperties_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestRequest_SessionProperties_Test.java
new file mode 100644
index 0000000000..4fcc05674d
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestRequest_SessionProperties_Test.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest;
+
+import java.util.*;
+
+import org.apache.juneau.TestBase;
+import org.apache.juneau.json.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.mock.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link RestRequest} session property methods.
+ */
+class RestRequest_SessionProperties_Test extends TestBase {
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Basic setter tests
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+ public static class A {
+ @RestGet(path="/test")
+ public String test(RestRequest req) {
+ req.setSerializerSessionProperty("key1", "value1");
+ var map = req.getSerializerSessionPropertyMap();
+ return map.get("key1") != null ? "ok" : "fail";
+ }
+ }
+
+ @Test
+ void a01_setSerializerSessionProperty_single() throws Exception {
+ MockRestClient.create(A.class).plainText().build()
+ .get("/test")
+ .run()
+ .assertContent("ok");
+ }
+
+ @Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+ public static class B {
+ @RestPost(path="/test")
+ public String test(RestRequest req) {
+ req.setParserSessionProperty("key1", "value1");
+ var map = req.getParserSessionPropertyMap();
+ return map.get("key1") != null ? "ok" : "fail";
+ }
+ }
+
+ @Test
+ void a02_setParserSessionProperty_single() throws Exception {
+ MockRestClient.create(B.class).plainText().build()
+ .post("/test", "")
+ .run()
+ .assertContent("ok");
+ }
+
+ @Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+ public static class C {
+ @RestGet(path="/test")
+ public String test(RestRequest req) {
+ var props = Map.<String,Object>of("key1", "value1",
"key2", "value2");
+ req.setSerializerSessionProperties(props);
+ var map = req.getSerializerSessionPropertyMap();
+ return map.get("key1") != null && map.get("key2") !=
null ? "ok" : "fail";
+ }
+ }
+
+ @Test
+ void a03_setSerializerSessionProperties_bulk() throws Exception {
+ MockRestClient.create(C.class).plainText().build()
+ .get("/test")
+ .run()
+ .assertContent("ok");
+ }
+
+ @Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+ public static class D {
+ @RestPost(path="/test")
+ public String test(RestRequest req) {
+ var props = Map.<String,Object>of("key1", "value1",
"key2", "value2");
+ req.setParserSessionProperties(props);
+ var map = req.getParserSessionPropertyMap();
+ return map.get("key1") != null && map.get("key2") !=
null ? "ok" : "fail";
+ }
+ }
+
+ @Test
+ void a04_setParserSessionProperties_bulk() throws Exception {
+ MockRestClient.create(D.class).plainText().build()
+ .post("/test", "")
+ .run()
+ .assertContent("ok");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Method chaining
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+ public static class E {
+ @RestGet(path="/test")
+ public String test(RestRequest req) {
+ req.setSerializerSessionProperty("key1", "value1")
+ .setSerializerSessionProperty("key2", "value2");
+ var map = req.getSerializerSessionPropertyMap();
+ return map.get("key1") != null && map.get("key2") !=
null ? "ok" : "fail";
+ }
+ }
+
+ @Test
+ void b01_methodChaining_serializer() throws Exception {
+ MockRestClient.create(E.class).plainText().build()
+ .get("/test")
+ .run()
+ .assertContent("ok");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Integration with HTTP options (query/header) and programmatic
override
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+ public static class G {
+ @RestGet(path="/test",
allowedSerializerOptions="useWhitespace,customKey",
allowedParserOptions="strict,customKey")
+ public String test(RestRequest req) {
+ req.setSerializerSessionProperty("customKey",
"customValue");
+ req.setParserSessionProperty("customKey",
"customValue");
+
+ var smap = req.getSerializerSessionPropertyMap();
+ var pmap = req.getParserSessionPropertyMap();
+
+ return "s:" + smap.get("customKey") + ",p:" +
pmap.get("customKey");
+ }
+ }
+
+ @Test
+ void c01_integration_programmatic_override() throws Exception {
+ MockRestClient.create(G.class).plainText().build()
+ .get("/test")
+ .run()
+ .assertContent("s:customValue,p:customValue");
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
index afc4ef9fd1..e4eefba02a 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
@@ -49,6 +49,9 @@ class RestAnnotation_Test extends TestBase {
//------------------------------------------------------------------------------------------------------------------
Rest a1 = RestAnnotation.create()
+ .allowedParserOptions("e1")
+ .allowedSerializerOptions("e2")
+ .noInherit("e3")
.disableContentParam("a")
.allowedHeaderParams("b")
.allowedMethodHeaders("c")
@@ -100,6 +103,9 @@ class RestAnnotation_Test extends TestBase {
.build();
Rest a2 = RestAnnotation.create()
+ .allowedParserOptions("e1")
+ .allowedSerializerOptions("e2")
+ .noInherit("e3")
.disableContentParam("a")
.allowedHeaderParams("b")
.allowedMethodHeaders("c")
@@ -152,8 +158,8 @@ class RestAnnotation_Test extends TestBase {
@Test void a01_basic() {
assertBean(a1,
-
"allowedHeaderParams,allowedMethodHeaders,allowedMethodParams,beanStore,callLogger,children,clientVersionHeader,config,consumes,converters,debug,debugEnablement,debugOn,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestHeaders,defaultResponseHeaders,description,disableContentParam,encoders,guards,maxInput,messages,on,onClass,parsers,partParser,partSerializer,path,produces,renderResponseStackTraces,responseProcessors,restChildrenClass,restOpArgs,rol
[...]
-
"b,c,d,BasicBeanStore,CallLogger,[RestAnnotation_Test],e,f,[g],[RestConverter],h,DebugEnablement,i,j,k,l,[m],[n],[o],[p],a,[Encoder],[RestGuard],q,r,[s],[RestAnnotation_Test],[Parser],HttpPartParser,HttpPartSerializer,t,[u],v,[ResponseProcessor],RestChildren,[RestOpArg],w,x,[Serializer],y,StaticFiles,{{[],,,},[],{[],},{[],,},[],[],[],[],},BasicSwaggerProvider,[z],aa,bb,cc,dd");
+
"allowedHeaderParams,allowedMethodHeaders,allowedMethodParams,allowedParserOptions,allowedSerializerOptions,beanStore,callLogger,children,clientVersionHeader,config,consumes,converters,debug,debugEnablement,debugOn,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestHeaders,defaultResponseHeaders,description,disableContentParam,encoders,guards,maxInput,messages,noInherit,on,onClass,parsers,partParser,partSerializer,path,produces,renderResponseStackTr
[...]
+
"b,c,d,[e1],[e2],BasicBeanStore,CallLogger,[RestAnnotation_Test],e,f,[g],[RestConverter],h,DebugEnablement,i,j,k,l,[m],[n],[o],[p],a,[Encoder],[RestGuard],q,r,[e3],[s],[RestAnnotation_Test],[Parser],HttpPartParser,HttpPartSerializer,t,[u],v,[ResponseProcessor],RestChildren,[RestOpArg],w,x,[Serializer],y,StaticFiles,{{[],,,},[],{[],},{[],,},[],[],[],[],},BasicSwaggerProvider,[z],aa,bb,cc,dd");
}
@Test void a02_testEquivalency() {
@@ -198,6 +204,9 @@ class RestAnnotation_Test extends TestBase {
//------------------------------------------------------------------------------------------------------------------
@Rest(
+ allowedParserOptions="e1",
+ allowedSerializerOptions="e2",
+ noInherit="e3",
disableContentParam="a",
allowedHeaderParams="b",
allowedMethodHeaders="c",
@@ -251,6 +260,9 @@ class RestAnnotation_Test extends TestBase {
Rest d1 = D1.class.getAnnotationsByType(Rest.class)[0];
@Rest(
+ allowedParserOptions="e1",
+ allowedSerializerOptions="e2",
+ noInherit="e3",
disableContentParam="a",
allowedHeaderParams="b",
allowedMethodHeaders="c",
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation_Test.java
index c8d9f9e4d6..41b07ac84d 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation_Test.java
@@ -38,6 +38,9 @@ class RestDeleteAnnotation_Test extends TestBase {
//------------------------------------------------------------------------------------------------------------------
RestDelete a1 = RestDeleteAnnotation.create()
+ .allowedParserOptions("p1")
+ .allowedSerializerOptions("p2")
+ .noInherit("p3")
.clientVersion("a")
.debug("b")
.defaultAccept("c")
@@ -60,6 +63,9 @@ class RestDeleteAnnotation_Test extends TestBase {
.build();
RestDelete a2 = RestDeleteAnnotation.create()
+ .allowedParserOptions("p1")
+ .allowedSerializerOptions("p2")
+ .noInherit("p3")
.clientVersion("a")
.debug("b")
.defaultAccept("c")
@@ -83,8 +89,8 @@ class RestDeleteAnnotation_Test extends TestBase {
@Test void a01_basic() {
assertBean(a1,
-
"clientVersion,debug,defaultAccept,defaultCharset,defaultRequestAttributes,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,on,path,roleGuard,rolesDeclared,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-
"a,b,c,d,[f],[g],[e],[h],[i],[Encoder],[RestGuard],[RestMatcher],[j],[k],l,m,n,{[],,[],{[],},,[],[],[],[],[],[],[]},o");
+
"allowedParserOptions,allowedSerializerOptions,clientVersion,debug,defaultAccept,defaultCharset,defaultRequestAttributes,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,noInherit,on,path,roleGuard,rolesDeclared,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
+
"[p1],[p2],a,b,c,d,[f],[g],[e],[h],[i],[Encoder],[RestGuard],[RestMatcher],[p3],[j],[k],l,m,n,{[],,[],{[],},,[],[],[],[],[],[],[]},o");
}
@Test void a02_testEquivalency() {
@@ -129,6 +135,9 @@ class RestDeleteAnnotation_Test extends TestBase {
public interface D1 {
@RestDelete(
+ allowedParserOptions="p1",
+ allowedSerializerOptions="p2",
+ noInherit="p3",
clientVersion="a",
debug="b",
defaultAccept="c",
@@ -152,6 +161,9 @@ class RestDeleteAnnotation_Test extends TestBase {
void m1();
@RestDelete(
+ allowedParserOptions="p1",
+ allowedSerializerOptions="p2",
+ noInherit="p3",
clientVersion="a",
debug="b",
defaultAccept="c",
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestGetAnnotation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestGetAnnotation_Test.java
index 39007815c3..98fc623d72 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestGetAnnotation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestGetAnnotation_Test.java
@@ -40,6 +40,9 @@ class RestGetAnnotation_Test extends TestBase {
//------------------------------------------------------------------------------------------------------------------
RestGet a1 = RestGetAnnotation.create()
+ .allowedParserOptions("q1")
+ .allowedSerializerOptions("q2")
+ .noInherit("q3")
.clientVersion("a")
.converters(RestConverter.class)
.debug("b")
@@ -65,6 +68,9 @@ class RestGetAnnotation_Test extends TestBase {
.build();
RestGet a2 = RestGetAnnotation.create()
+ .allowedParserOptions("q1")
+ .allowedSerializerOptions("q2")
+ .noInherit("q3")
.clientVersion("a")
.converters(RestConverter.class)
.debug("b")
@@ -91,8 +97,8 @@ class RestGetAnnotation_Test extends TestBase {
@Test void a01_basic() {
assertBean(a1,
-
"clientVersion,converters,debug,defaultAccept,defaultCharset,defaultRequestAttributes,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,on,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-
"a,[RestConverter],b,c,d,[f],[g],[e],[h],[i],[Encoder],[RestGuard],[RestMatcher],[j],[k],[l],m,n,[Serializer],o,{[],,[],{[],},,[],[],[],[],[],[],[]},p");
+
"allowedParserOptions,allowedSerializerOptions,clientVersion,converters,debug,defaultAccept,defaultCharset,defaultRequestAttributes,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,noInherit,on,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
+
"[q1],[q2],a,[RestConverter],b,c,d,[f],[g],[e],[h],[i],[Encoder],[RestGuard],[RestMatcher],[q3],[j],[k],[l],m,n,[Serializer],o,{[],,[],{[],},,[],[],[],[],[],[],[]},p");
}
@Test void a02_testEquivalency() {
@@ -137,6 +143,9 @@ class RestGetAnnotation_Test extends TestBase {
public interface D1 {
@RestGet(
+ allowedParserOptions="q1",
+ allowedSerializerOptions="q2",
+ noInherit="q3",
clientVersion="a",
converters=RestConverter.class,
debug="b",
@@ -163,6 +172,9 @@ class RestGetAnnotation_Test extends TestBase {
void m1();
@RestGet(
+ allowedParserOptions="q1",
+ allowedSerializerOptions="q2",
+ noInherit="q3",
clientVersion="a",
converters=RestConverter.class,
debug="b",
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
index e44ed91c05..2fec081b9b 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
@@ -41,6 +41,9 @@ class RestOpAnnotation_Test extends TestBase {
//------------------------------------------------------------------------------------------------------------------
RestOp a1 = RestOpAnnotation.create()
+ .allowedParserOptions("v1")
+ .allowedSerializerOptions("v2")
+ .noInherit("v3")
.clientVersion("a")
.consumes("b")
.converters(RestConverter.class)
@@ -72,6 +75,9 @@ class RestOpAnnotation_Test extends TestBase {
.build();
RestOp a2 = RestOpAnnotation.create()
+ .allowedParserOptions("v1")
+ .allowedSerializerOptions("v2")
+ .noInherit("v3")
.clientVersion("a")
.consumes("b")
.converters(RestConverter.class)
@@ -104,8 +110,8 @@ class RestOpAnnotation_Test extends TestBase {
@Test void a01_basic() {
assertBean(a1,
-
"clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,method,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-
"a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,n,[o],[Parser],[p],[q],r,s,[Serializer],t,{[],,[],{[],},,[],[],[],[],[],[],[]},u");
+
"allowedParserOptions,allowedSerializerOptions,clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,method,noInherit,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,res
[...]
+
"[v1],[v2],a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,n,[v3],[o],[Parser],[p],[q],r,s,[Serializer],t,{[],,[],{[],},,[],[],[],[],[],[],[]},u");
}
@Test void a02_testEquivalency() {
@@ -150,6 +156,9 @@ class RestOpAnnotation_Test extends TestBase {
public interface D1 {
@RestOp(
+ allowedParserOptions="v1",
+ allowedSerializerOptions="v2",
+ noInherit="v3",
clientVersion="a",
consumes="b",
converters=RestConverter.class,
@@ -182,6 +191,9 @@ class RestOpAnnotation_Test extends TestBase {
void m1();
@RestOp(
+ allowedParserOptions="v1",
+ allowedSerializerOptions="v2",
+ noInherit="v3",
clientVersion="a",
consumes="b",
converters=RestConverter.class,
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPostAnnotation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPostAnnotation_Test.java
index 8ed6188fd7..54b27b2e05 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPostAnnotation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPostAnnotation_Test.java
@@ -41,6 +41,9 @@ class RestPostAnnotation_Test extends TestBase {
//------------------------------------------------------------------------------------------------------------------
RestPost a1 = RestPostAnnotation.create()
+ .allowedParserOptions("u1")
+ .allowedSerializerOptions("u2")
+ .noInherit("u3")
.clientVersion("a")
.consumes("b")
.converters(RestConverter.class)
@@ -71,6 +74,9 @@ class RestPostAnnotation_Test extends TestBase {
.build();
RestPost a2 = RestPostAnnotation.create()
+ .allowedParserOptions("u1")
+ .allowedSerializerOptions("u2")
+ .noInherit("u3")
.clientVersion("a")
.consumes("b")
.converters(RestConverter.class)
@@ -102,8 +108,8 @@ class RestPostAnnotation_Test extends TestBase {
@Test void a01_basic() {
assertBean(a1,
-
"clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-
"a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,[n],[Parser],[o],[p],q,r,[Serializer],s,{[],,[],{[],},,[],[],[],[],[],[],[]},t");
+
"allowedParserOptions,allowedSerializerOptions,clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,noInherit,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,
[...]
+
"[u1],[u2],a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,[u3],[n],[Parser],[o],[p],q,r,[Serializer],s,{[],,[],{[],},,[],[],[],[],[],[],[]},t");
}
@Test void a02_testEquivalency() {
@@ -148,6 +154,9 @@ class RestPostAnnotation_Test extends TestBase {
public interface D1 {
@RestPost(
+ allowedParserOptions="u1",
+ allowedSerializerOptions="u2",
+ noInherit="u3",
clientVersion="a",
consumes="b",
converters=RestConverter.class,
@@ -179,6 +188,9 @@ class RestPostAnnotation_Test extends TestBase {
void m1();
@RestPost(
+ allowedParserOptions="u1",
+ allowedSerializerOptions="u2",
+ noInherit="u3",
clientVersion="a",
consumes="b",
converters=RestConverter.class,
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPutAnnotation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPutAnnotation_Test.java
index 97bafdd1a2..b6f1a433b2 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPutAnnotation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPutAnnotation_Test.java
@@ -41,6 +41,9 @@ class RestPutAnnotation_Test extends TestBase {
//------------------------------------------------------------------------------------------------------------------
RestPut a1 = RestPutAnnotation.create()
+ .allowedParserOptions("u1")
+ .allowedSerializerOptions("u2")
+ .noInherit("u3")
.clientVersion("a")
.consumes("b")
.converters(RestConverter.class)
@@ -71,6 +74,9 @@ class RestPutAnnotation_Test extends TestBase {
.build();
RestPut a2 = RestPutAnnotation.create()
+ .allowedParserOptions("u1")
+ .allowedSerializerOptions("u2")
+ .noInherit("u3")
.clientVersion("a")
.consumes("b")
.converters(RestConverter.class)
@@ -102,8 +108,8 @@ class RestPutAnnotation_Test extends TestBase {
@Test void a01_basic() {
assertBean(a1,
-
"clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-
"a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,[n],[Parser],[o],[p],q,r,[Serializer],s,{[],,[],{[],},,[],[],[],[],[],[],[]},t");
+
"allowedParserOptions,allowedSerializerOptions,clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,noInherit,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,
[...]
+
"[u1],[u2],a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,[u3],[n],[Parser],[o],[p],q,r,[Serializer],s,{[],,[],{[],},,[],[],[],[],[],[],[]},t");
}
@Test void a02_testEquivalency() {
@@ -148,6 +154,9 @@ class RestPutAnnotation_Test extends TestBase {
public interface D1 {
@RestPut(
+ allowedParserOptions="u1",
+ allowedSerializerOptions="u2",
+ noInherit="u3",
clientVersion="a",
consumes="b",
converters=RestConverter.class,
@@ -179,6 +188,9 @@ class RestPutAnnotation_Test extends TestBase {
void m1();
@RestPut(
+ allowedParserOptions="u1",
+ allowedSerializerOptions="u2",
+ noInherit="u3",
clientVersion="a",
consumes="b",
converters=RestConverter.class,
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Headers_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Headers_Test.java
index d8280b9edf..99e8caed29 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Headers_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Headers_Test.java
@@ -22,8 +22,10 @@ import static org.apache.juneau.TestUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
import static org.apache.juneau.http.HttpHeaders.*;
import static org.apache.juneau.httppart.HttpPartSchema.*;
+import static org.apache.juneau.rest.RestSharedConstants.*;
import java.time.*;
+import java.util.Map;
import org.apache.juneau.*;
import org.apache.juneau.http.header.*;
@@ -69,7 +71,7 @@ public class RestClient_Headers_Test extends TestBase {
@Rest(callLogger=CaptureLogger.class)
public static class A extends BasicRestObject {
- @RestGet
+ @RestGet(allowedSerializerOptions="simple",
allowedParserOptions="addBeanTypes")
public String[] headers(org.apache.juneau.rest.RestRequest req)
{
return
req.getHeaders().getAll(req.getHeaderParam("Check").orElse(null)).stream().map(RequestHeader::getValue).toArray(String[]::new);
}
@@ -82,6 +84,12 @@ public class RestClient_Headers_Test extends TestBase {
// Method tests
//------------------------------------------------------------------------------------------------------------------
+ @Test void a00_sessionOptionHeaders() throws Exception {
+
checkFooClient().serializerSessionOptionsHeader("{simple:true}").build().get("/headers").header("Check",
HEADER_JuneauSerializerOptions).run().assertContent("[\"{simple:true}\"]");
+
checkFooClient().parserSessionOptionsHeader("{addBeanTypes:false}").build().get("/headers").header("Check",
HEADER_JuneauParserOptions).run().assertContent("[\"{addBeanTypes:false}\"]");
+
checkFooClient().build().get("/headers").serializerSessionOptionsHeader(Map.of("simple",
true)).header("Check",
HEADER_JuneauSerializerOptions).run().assertContent().asString().isContains("simple");
+ }
+
@Test void a01_header_String_Object() throws Exception {
checkFooClient().header("Foo","bar").build().get("/headers").run().assertContent("[\"bar\"]");
checkFooClient().build().get("/headers").header("Foo","baz").run().assertContent("[\"baz\"]");
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
index a8b5fc2964..ef3de9484b 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
@@ -20,6 +20,7 @@ import static org.apache.juneau.TestUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
import static org.apache.juneau.http.HttpParts.*;
import static org.apache.juneau.httppart.HttpPartSchema.*;
+import static org.apache.juneau.rest.RestSharedConstants.*;
import java.io.*;
@@ -50,6 +51,15 @@ class RestClient_Query_Test extends TestBase {
// Method tests
//------------------------------------------------------------------------------------------------------------------
+ @Test void a00_sessionOptionQuery() throws Exception {
+
client().serializerSessionOptionsQueryDefault("(simple=true)").build().get("/query").run().assertContent().asString().asUrlDecode()
+ .isContains(QUERY_juneauSerializerOptions +
"=(simple=true)");
+
client().parserSessionOptionsQueryDefault("(strict=true)").build().get("/query").run().assertContent().asString().asUrlDecode()
+ .isContains(QUERY_juneauParserOptions +
"=(strict=true)");
+
client().build().get("/query").serializerSessionOptionsQuery("(a=1)").run().assertContent().asString().asUrlDecode()
+ .isContains(QUERY_juneauSerializerOptions + "=(a=1)");
+ }
+
@Test void a01_query_String_Object() throws Exception {
client().queryData("foo","bar").queryData(part("foo",new
StringBuilder("baz"),null)).build().get("/query").run().assertContent("foo=bar&foo=baz");
client().build().get("/query").queryData("foo","bar").run().assertContent().isContains("foo=bar");