This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch jbFixRestNpe
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/jbFixRestNpe by this push:
new 146cdccf0 Fix bug in @RemoteX annotations.
146cdccf0 is described below
commit 146cdccf0896a0bc2533c5baa2f396d860ea842d
Author: JamesBognar <[email protected]>
AuthorDate: Sat Aug 13 12:21:41 2022 -0400
Fix bug in @RemoteX annotations.
---
.../org/apache/juneau/reflect/AnnotationInfo.java | 10 +
.../rest/client/remote/RemoteOperationMeta.java | 5 +-
.../org/apache/juneau/http/remote/RemotePatch.java | 125 ++++
.../apache/juneau/rest/annotation/RestOptions.java | 727 +++++++++++++++++++++
.../rest/annotation/RestOptionsAnnotation.java | 538 +++++++++++++++
.../remote/Remote_FormDataAnnotation_Test.java | 106 +--
.../org/apache/juneau/http/remote/Remote_Test.java | 103 +++
7 files changed, 1559 insertions(+), 55 deletions(-)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/AnnotationInfo.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/AnnotationInfo.java
index a328cb925..02f571294 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/AnnotationInfo.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/AnnotationInfo.java
@@ -148,6 +148,16 @@ public final class AnnotationInfo<T extends Annotation> {
return a;
}
+ /**
+ * Returns the class name of the annotation.
+ *
+ * @return The simple class name of the annotation.
+ */
+ public String getName() {
+ return a.annotationType().getSimpleName();
+ }
+
+
/**
* Converts this object to a readable JSON object for debugging
purposes.
*
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
index 2b22594e3..fdd43253a 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
@@ -92,7 +92,8 @@ public class RemoteOperationMeta {
al =
mi.getReturnType().unwrap(Value.class,Optional.class).getAnnotationList(REMOTE_OP_GROUP);
Value<String> _httpMethod = Value.empty(), _path =
Value.empty();
- al.forEachValue(String.class, "method", NOT_EMPTY, x ->
_httpMethod.set(x.trim()));
+ al.stream().map(x ->
x.getName().substring(6).toUpperCase()).filter(x -> ! x.equals("OP")).forEach(x
-> _httpMethod.set(x));
+ al.forEachValue(String.class, "method", NOT_EMPTY, x ->
_httpMethod.set(x.trim().toUpperCase()));
al.forEachValue(String.class, "path", NOT_EMPTY, x->
_path.set(x.trim()));
httpMethod = _httpMethod.orElse("").trim();
path = _path.orElse("").trim();
@@ -125,7 +126,7 @@ public class RemoteOperationMeta {
if (! isOneOf(httpMethod, "DELETE", "GET", "POST",
"PUT", "OPTIONS", "HEAD", "CONNECT", "TRACE", "PATCH"))
throw new RemoteMetadataException(m,
- "Invalid value specified for
@RemoteOp(httpMethod) annotation. Valid values are
[DELTE,GET,POST,PUT,OPTIONS,HEAD,CONNECT,TRACE,PATCH].");
+ "Invalid value specified for
@RemoteOp(httpMethod) annotation: '"+httpMethod+"'. Valid values are
[DELETE,GET,POST,PUT,OPTIONS,HEAD,CONNECT,TRACE,PATCH].");
methodReturn = new RemoteOperationReturn(mi);
diff --git
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/remote/RemotePatch.java
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/remote/RemotePatch.java
new file mode 100644
index 000000000..edf2e20a0
--- /dev/null
+++
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/remote/RemotePatch.java
@@ -0,0 +1,125 @@
+//
***************************************************************************************************************************
+// * 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.http.remote;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.io.*;
+import java.lang.annotation.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.annotation.*;
+
+/**
+ * Annotation applied to Java methods on REST proxy interface classes.
+ *
+ * <p>
+ * Note that this annotation is optional if you do not need to override any of
the values.
+ *
+ * <ul class='seealso'>
+ * <li class='link'>{@doc jrc.Proxies}
+ * <li class='extlink'>{@source}
+ * </ul>
+ */
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+@Inherited
+@AnnotationGroup(RemoteOp.class)
+public @interface RemotePatch {
+
+ /**
+ * REST service path.
+ *
+ * <p>
+ * If you do not specify a path, then the path is inferred from the
Java method name.
+ *
+ * <h5 class='figure'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// PATCH /pet</jc>
+ * <ja>@RemotePatch</ja>
+ * <jk>public void</jk> patchPet(...);
+ * </p>
+ *
+ * <p>
+ * Note that you can also use {@link #value()} to specify the path in
shortened form.
+ *
+ * <ul class='values'>
+ * <li>An absolute URL.
+ * <li>A relative URL interpreted as relative to the root URL
defined on the <c>RestClient</c> and/or {@link Remote#path()}.
+ * <li>No path.
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String path() default "";
+
+ /**
+ * The value the remote method returns.
+ *
+ * <ul class='values'>
+ * <li>
+ * {@link RemoteReturn#BODY} (default) - The body of the
HTTP response converted to a POJO.
+ * <br>The return type on the Java method can be any of
the following:
+ * <ul class='spaced-list'>
+ * <li>
+ * <jk>void</jk> - Don't parse any
response. Note that the method will still throw an exception if an
+ * error HTTP status is returned.
+ * <li>
+ * Any parsable POJO - The body of the
response will be converted to the POJO using the parser defined
+ * on the <c>RestClient</c>.
+ * <li>
+ * Any POJO annotated with the {@link
Response @Response} annotation.
+ * This allows for response beans to be
used which also allows for OpenAPI-based parsing and validation.
+ * <li>
+ * <c>HttpResponse</c> - Returns the raw
<c>HttpResponse</c> returned by the inner
+ * <c>HttpClient</c>.
+ * <li>
+ * {@link Reader} - Returns access to the
raw reader of the response.
+ * <li>
+ * {@link InputStream} - Returns access to
the raw input stream of the response.
+ * </ul>
+ * <li>
+ * {@link RemoteReturn#STATUS} - The HTTP status code on
the response.
+ * <br>The return type on the Java method can be any of
the following:
+ * <ul>
+ * <li><jk>int</jk>/<c>Integer</c> - The HTTP
response code.
+ * <li><jk>boolean</jk>/<c>Boolean</c> -
<jk>true</jk> if the response code is <c><400</c>
+ * </ul>
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ RemoteReturn returns() default RemoteReturn.BODY;
+
+ /**
+ * REST path.
+ *
+ * <p>
+ * Can be used to provide a shortened form for the {@link #path()}
value.
+ *
+ * <p>
+ * The following examples are considered equivalent.
+ * <p class='bjava'>
+ * <jc>// Normal form</jc>
+ * <ja>@RemotePatch</ja>(path=<js>"/{propertyName}"</js>)
+ *
+ * <jc>// Shortened form</jc>
+ * <ja>@RemotePatch</ja>(<js>"/{propertyName}"</js>)
+ * </p>
+ *
+ * @return The annotation value.
+ */
+ String value() default "";
+}
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
new file mode 100644
index 000000000..d235a33fe
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptions.java
@@ -0,0 +1,727 @@
+//
***************************************************************************************************************************
+// * 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.annotation;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.*;
+import java.nio.charset.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.converter.*;
+import org.apache.juneau.rest.guard.*;
+import org.apache.juneau.rest.httppart.*;
+import org.apache.juneau.rest.matcher.*;
+import org.apache.juneau.rest.servlet.*;
+import org.apache.juneau.rest.swagger.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.dto.swagger.*;
+import org.apache.juneau.encoders.*;
+
+/**
+ * Identifies a REST OPTIONS operation Java method on a {@link RestServlet}
implementation class.
+ *
+ * <p>
+ * This is a specialized subtype of <c><ja>{@link RestOp
@RestOp}(method=<jsf>OPTIONS</jsf>)</c>.
+ *
+ * <ul class='seealso'>
+ * <li class='link'>{@doc jrs.RestOpAnnotatedMethods}
+ * <li class='extlink'>{@source}
+ * </ul>
+ */
+@Target(METHOD)
+@Retention(RUNTIME)
+@Inherited
+@ContextApply(RestGetAnnotation.RestOpContextApply.class)
+@AnnotationGroup(RestOp.class)
+public @interface RestOptions {
+
+ /**
+ * Specifies whether this method can be called based on the client
version.
+ *
+ * <p>
+ * The client version is identified via the HTTP request header
identified by
+ * {@link Rest#clientVersionHeader() @Rest(clientVersionHeader)} which
by default is <js>"Client-Version"</js>.
+ *
+ * <p>
+ * This is a specialized kind of {@link RestMatcher} that allows you to
invoke different Java methods for the same
+ * method/path based on the client version.
+ *
+ * <p>
+ * The format of the client version range is similar to that of OSGi
versions.
+ *
+ * <p>
+ * In the following example, the Java methods are mapped to the same
HTTP method and URL <js>"/foobar"</js>.
+ * <p class='bjava'>
+ * <jc>// Call this method if Client-Version is at least 2.0.
+ * // Note that this also matches 2.0.1.</jc>
+ * <ja>@RestOptions</ja>(path=<js>"/foobar"</js>,
clientVersion=<js>"2.0"</js>)
+ * <jk>public</jk> Object method1() {...}
+ *
+ * <jc>// Call this method if Client-Version is at least 1.1, but
less than 2.0.</jc>
+ * <ja>@RestOptions</ja>(path=<js>"/foobar"</js>,
clientVersion=<js>"[1.1,2.0)"</js>)
+ * <jk>public</jk> Object method2() {...}
+ *
+ * <jc>// Call this method if Client-Version is less than 1.1.</jc>
+ * <ja>@RestOptions</ja>(path=<js>"/foobar"</js>,
clientVersion=<js>"[0,1.1)"</js>)
+ * <jk>public</jk> Object method3() {...}
+ * </p>
+ *
+ * <p>
+ * It's common to combine the client version with transforms that will
convert new POJOs into older POJOs for
+ * backwards compatibility.
+ * <p class='bjava'>
+ * <jc>// Call this method if Client-Version is at least 2.0.</jc>
+ * <ja>@RestOptions</ja>(path=<js>"/foobar"</js>,
clientVersion=<js>"2.0"</js>)
+ * <jk>public</jk> NewPojo newMethod() {...}
+ *
+ * <jc>// Call this method if Client-Version is at least 1.1, but
less than 2.0.</jc>
+ * <ja>@RestOptions</ja>(path=<js>"/foobar"</js>,
clientVersion=<js>"[1.1,2.0)"</js>)
+ * <ja>@BeanConfig(swaps=NewToOldSwap.<jk>class</jk>)
+ * <jk>public</jk> NewPojo oldMethod() {
+ * <jk>return</jk> newMethod();
+ * }
+ * </p>
+ *
+ * <p>
+ * Note that in the previous example, we're returning the exact same
POJO, but using a transform to convert it into
+ * an older form.
+ * The old method could also just return back a completely different
object.
+ * The range can be any of the following:
+ * <ul>
+ * <li><js>"[0,1.0)"</js> = Less than 1.0. 1.0 and 1.0.0 does not
match.
+ * <li><js>"[0,1.0]"</js> = Less than or equal to 1.0. Note that
1.0.1 will match.
+ * <li><js>"1.0"</js> = At least 1.0. 1.0 and 2.0 will match.
+ * </ul>
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestContext.Builder#clientVersionHeader(String)}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String clientVersion() default "";
+
+ /**
+ * Class-level response converters.
+ *
+ * <p>
+ * Associates one or more {@link RestConverter converters} with this
method.
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestOpContext.Builder#converters()} - Registering
converters with REST resources.
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ Class<? extends RestConverter>[] converters() default {};
+
+ /**
+ * Enable debug mode.
+ *
+ * <p>
+ * Enables the following:
+ * <ul class='spaced-list'>
+ * <li>
+ * HTTP request/response bodies are cached in memory for
logging purposes.
+ * <li>
+ * Request/response messages are automatically logged.
+ * </ul>
+ *
+ * <ul class='values'>
+ * <li><js>"true"</js> - Debug is enabled for all requests.
+ * <li><js>"false"</js> - Debug is disabled for all requests.
+ * <li><js>"conditional"</js> - Debug is enabled only for requests
that have a <c class='snippet'>Debug: true</c> header.
+ * <li><js>""</js> (or anything else) - Debug mode is inherited
from class.
+ * </ul>
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$L{my.localized.variable}"</js>).
+ * </ul>
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestContext.Builder#debugEnablement()}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String debug() default "";
+
+ /**
+ * Default <c>Accept</c> header.
+ *
+ * <p>
+ * The default value for the <c>Accept</c> header if not specified on a
request.
+ *
+ * <p>
+ * This is a shortcut for using {@link #defaultRequestHeaders()} for
just this specific header.
+ *
+ * @return The annotation value.
+ */
+ String defaultAccept() default "";
+
+ /**
+ * Default character encoding.
+ *
+ * <p>
+ * The default character encoding for the request and response if not
specified on the request.
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$S{mySystemProperty}"</js>).
+ * </ul>
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestContext.Builder#defaultCharset(Charset)}
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestOpContext.Builder#defaultCharset(Charset)}
+ * <li class='ja'>{@link Rest#defaultCharset}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String defaultCharset() default "";
+
+ /**
+ * Specifies default values for query parameters.
+ *
+ * <p>
+ * Strings are of the format <js>"name=value"</js>.
+ *
+ * <p>
+ * Affects values returned by {@link RestRequest#getQueryParam(String)}
when the parameter is not present on the request.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <ja>@RestOptions</ja>(path=<js>"/*"</js>,
defaultRequestQueryData={<js>"foo=bar"</js>})
+ * <jk>public</jk> String doGet(<ja>@Query</ja>(<js>"foo"</js>)
String <jv>foo</jv>) {...}
+ * </p>
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * You can use either <js>':'</js> or <js>'='</js> as the
key/value delimiter.
+ * <li class='note'>
+ * Key and value is trimmed of whitespace.
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$S{mySystemProperty}"</js>).
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] defaultRequestQueryData() default {};
+
+ /**
+ * Default request attributes.
+ *
+ * <p>
+ * Specifies default values for request attributes if they're not
already set on the request.
+ *
+ * <p>
+ * Affects values returned by the following methods:
+ * <ul>
+ * <li class='jm'>{@link RestRequest#getAttribute(String)}.
+ * <li class='jm'>{@link RestRequest#getAttributes()}.
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Defined via annotation resolving to a config file
setting with default value.</jc>
+ * <ja>@Rest</ja>(defaultRequestAttributes={<js>"Foo=bar"</js>,
<js>"Baz: $C{REST/myAttributeValue}"</js>})
+ * <jk>public class</jk> MyResource {
+ *
+ * <jc>// Override at the method level.</jc>
+ *
<ja>@RestOptions</ja>(defaultRequestAttributes={<js>"Foo: bar"</js>})
+ * <jk>public</jk> Object myMethod() {...}
+ * }
+ * </p>
+ *
+ * </ul>
+ * <ul class='notes'>
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$L{my.localized.variable}"</js>).
+ * </ul>
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestContext.Builder#defaultRequestAttributes(NamedAttribute...)}
+ * <li class='ja'>{@link Rest#defaultRequestAttributes()}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] defaultRequestAttributes() default {};
+
+ /**
+ * Default request headers.
+ *
+ * <p>
+ * Specifies default values for request headers if they're not passed
in through the request.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Assume "text/json" Accept value when Accept not
specified</jc>
+ * <ja>@RestOptions</ja>(path=<js>"/*"</js>,
defaultRequestHeaders={<js>"Accept: text/json"</js>})
+ * <jk>public</jk> String doGet() {...}
+ * </p>
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$S{mySystemProperty}"</js>).
+ * </ul>
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestContext.Builder#defaultRequestHeaders(org.apache.http.Header...)}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] defaultRequestHeaders() default {};
+
+ /**
+ * Default response headers.
+ *
+ * <p>
+ * Specifies default values for response headers if they're not
overwritten during the request.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Assume "text/json" Accept value when Accept not
specified</jc>
+ * <ja>@RestOptions</ja>(path=<js>"/*"</js>,
defaultResponseHeaders={<js>"Content-Type: text/json"</js>})
+ * <jk>public</jk> String doGet() {...}
+ * </p>
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$S{mySystemProperty}"</js>).
+ * </ul>
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestContext.Builder#defaultResponseHeaders(org.apache.http.Header...)}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] defaultResponseHeaders() default {};
+
+ /**
+ * Optional description for the exposed API.
+ *
+ * <p>
+ * This description is used in the following locations:
+ * <ul class='spaced-list'>
+ * <li>
+ * The value returned by {@link
Operation#getDescription()} in the auto-generated swagger.
+ * <li>
+ * The <js>"$RS{operationDescription}"</js> variable.
+ * <li>
+ * The description of the method in the Swagger page.
+ * </ul>
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * Corresponds to the swagger field
<c>/paths/{path}/{method}/description</c>.
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$L{my.localized.variable}"</js>).
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] description() default {};
+
+ /**
+ * Specifies the compression encoders for this method.
+ *
+ * <p>
+ * Encoders are used to enable various kinds of compression (e.g.
<js>"gzip"</js>) on requests and responses.
+ *
+ * <p>
+ * This value overrides encoders specified at the class level using
{@link Rest#encoders()}.
+ * The {@link org.apache.juneau.encoders.EncoderSet.Inherit} class can
be used to include values from the parent class.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Define a REST resource that handles GZIP
compression.</jc>
+ * <ja>@Rest</ja>(
+ * encoders={
+ * GzipEncoder.<jk>class</jk>
+ * }
+ * )
+ * <jk>public class</jk> MyResource {
+ *
+ * <jc>// Define a REST method that can also use a custom
encoder.</jc>
+ * <ja>@RestOptions</ja>(
+ * method=<jsf>GET</jsf>,
+ * encoders={
+ * EncoderSet.Inherit.<jk>class</jk>,
MyEncoder.<jk>class</jk>
+ * }
+ * )
+ * <jk>public</jk> MyBean doGet() {
+ * ...
+ * }
+ * }
+ * </p>
+ *
+ * <p>
+ * The programmatic equivalent to this annotation is:
+ * <p class='bjava'>
+ * RestOpContext.Builder <jv>builder</jv> =
RestOpContext.<jsm>create</jsm>(<jv>method</jv>,<jv>restContext</jv>);
+ * <jv>builder</jv>.getEncoders().set(<jv>classes</jv>);
+ * </p>
+ *
+ * <ul class='seealso'>
+ * <li class='link'>{@doc jrs.Encoders}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ Class<? extends Encoder>[] encoders() default {};
+
+ /**
+ * Method-level guards.
+ *
+ * <p>
+ * Associates one or more {@link RestGuard RestGuards} with this method.
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestOpContext.Builder#guards()}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ Class<? extends RestGuard>[] guards() default {};
+
+ /**
+ * Method matchers.
+ *
+ * <p>
+ * Associates one more more {@link RestMatcher RestMatchers} with this
method.
+ *
+ * <p>
+ * Matchers are used to allow multiple Java methods to handle requests
assigned to the same URL path pattern, but
+ * differing based on some request attribute, such as a specific header
value.
+ *
+ * <ul class='seealso'>
+ * <li class='jac'>{@link RestMatcher}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ Class<? extends RestMatcher>[] matchers() default {};
+
+ /**
+ * Dynamically apply this annotation to the specified methods.
+ *
+ * <ul class='seealso'>
+ * <li class='link'>{@doc jm.DynamicallyAppliedAnnotations}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] on() default {};
+
+ /**
+ * Optional path pattern for the specified method.
+ *
+ * <p>
+ * Appending <js>"/*"</js> to the end of the path pattern will make it
match any remainder too.
+ * <br>Not appending <js>"/*"</js> to the end of the pattern will cause
a 404 (Not found) error to occur if the exact
+ * pattern is not found.
+ *
+ * <p>
+ * The path can contain variables that get resolved to {@link
org.apache.juneau.http.annotation.Path @Path} parameters.
+ *
+ * <h5 class='figure'>Examples:</h5>
+ * <p class='bjava'>
+ *
<ja>@RestOptions</ja>(path=<js>"/myurl/{foo}/{bar}/{baz}/*"</js>)
+ * </p>
+ * <p class='bjava'>
+ * <ja>@RestOptions</ja>(path=<js>"/myurl/{0}/{1}/{2}/*"</js>)
+ * </p>
+ *
+ * <p>
+ * Note that you can also use {@link #value()} to specify the path.
+ *
+ * <ul class='seealso'>
+ * <li class='ja'>{@link org.apache.juneau.http.annotation.Path}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] path() default {};
+
+ /**
+ * Supported accept media types.
+ *
+ * <p>
+ * Overrides the media types inferred from the serializers that
identify what media types can be produced by the resource.
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$S{mySystemProperty}"</js>).
+ * </ul>
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestOpContext.Builder#produces(MediaType...)}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String[] produces() default {};
+
+ /**
+ * Role guard.
+ *
+ * <p>
+ * An expression defining if a user with the specified roles are
allowed to access this method.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>public class</jk> MyResource <jk>extends</jk>
BasicRestServlet <jk>implements</jk> BasicUniversalConfig {
+ *
+ * <ja>@RestOptions</ja>(
+ * path=<js>"/foo"</js>,
+ * roleGuard=<js>"ROLE_ADMIN || (ROLE_READ_WRITE
&& ROLE_SPECIAL)"</js>
+ * )
+ * <jk>public</jk> Object doGet() {
+ * }
+ * }
+ * </p>
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * Supports any of the following expression constructs:
+ * <ul>
+ * <li><js>"foo"</js> - Single arguments.
+ * <li><js>"foo,bar,baz"</js> - Multiple OR'ed
arguments.
+ * <li><js>"foo | bar | baz"</js> - Multiple OR'ed
arguments, pipe syntax.
+ * <li><js>"foo || bar || baz"</js> - Multiple
OR'ed arguments, Java-OR syntax.
+ * <li><js>"fo*"</js> - Patterns including
<js>'*'</js> and <js>'?'</js>.
+ * <li><js>"fo* & *oo"</js> - Multiple AND'ed
arguments, ampersand syntax.
+ * <li><js>"fo* && *oo"</js> - Multiple
AND'ed arguments, Java-AND syntax.
+ * <li><js>"fo* || (*oo || bar)"</js> -
Parenthesis.
+ * </ul>
+ * <li class='note'>
+ * AND operations take precedence over OR operations (as
expected).
+ * <li class='note'>
+ * Whitespace is ignored.
+ * <li class='note'>
+ * <jk>null</jk> or empty expressions always match as
<jk>false</jk>.
+ * <li class='note'>
+ * If patterns are used, you must specify the list of
declared roles using {@link #rolesDeclared()} or {@link
org.apache.juneau.rest.RestOpContext.Builder#rolesDeclared(String...)}.
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$L{my.localized.variable}"</js>).
+ * <li class='note'>
+ * When defined on parent/child classes and methods, ALL
guards within the hierarchy must pass.
+ * </ul>
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestOpContext.Builder#roleGuard(String)}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String roleGuard() default "";
+
+ /**
+ * Declared roles.
+ *
+ * <p>
+ * A comma-delimited list of all possible user roles.
+ *
+ * <p>
+ * Used in conjunction with {@link #roleGuard()} is used with patterns.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>public class</jk> MyResource <jk>extends</jk>
BasicRestServlet <jk>implements</jk> BasicUniversalConfig {
+ *
+ * <ja>@RestOptions</ja>(
+ * path=<js>"/foo"</js>,
+ *
rolesDeclared=<js>"ROLE_ADMIN,ROLE_READ_WRITE,ROLE_READ_ONLY,ROLE_SPECIAL"</js>,
+ * roleGuard=<js>"ROLE_ADMIN || (ROLE_READ_WRITE
&& ROLE_SPECIAL)"</js>
+ * )
+ * <jk>public</jk> Object doGet() {
+ * }
+ * }
+ * </p>
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.rest.RestOpContext.Builder#rolesDeclared(String...)}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String rolesDeclared() default "";
+
+ /**
+ * Specifies the serializers for marshalling POJOs into response bodies
for this method.
+ *
+ * <p>
+ * Serializer are used to convert POJOs to HTTP response bodies.
+ * <br>Any of the Juneau framework serializers can be used in this
setting.
+ * <br>The serializer selected is based on the request <c>Accept</c>
header matched against the values returned by the following method
+ * using a best-match algorithm:
+ * <ul class='javatree'>
+ * <li class='jm'>{@link Serializer#getMediaTypeRanges()}
+ * </ul>
+ *
+ * <p>
+ * This value overrides serializers specified at the class level using
{@link Rest#serializers()}.
+ * The {@link org.apache.juneau.serializer.SerializerSet.Inherit} class
can be used to include values from the parent class.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Define a REST resource that can produce JSON and
HTML.</jc>
+ * <ja>@Rest</ja>(
+ * serializers={
+ * JsonParser.<jk>class</jk>,
+ * HtmlParser.<jk>class</jk>
+ * }
+ * )
+ * <jk>public class</jk> MyResource {
+ *
+ * <jc>// Define a REST method that can also produce
XML.</jc>
+ * <ja>@RestOptions</ja>(
+ * parsers={
+ * SerializerSet.Inherit.<jk>class</jk>,
XmlParser.<jk>class</jk>
+ * }
+ * )
+ * <jk>public</jk> MyBean doGet() {
+ * ...
+ * }
+ * }
+ * </p>
+ *
+ * <p>
+ * The programmatic equivalent to this annotation is:
+ * <p class='bjava'>
+ * RestOpContext.Builder <jv>builder</jv> =
RestOpContext.<jsm>create</jsm>(<jv>method</jv>,<jv>restContext</jv>);
+ * <jv>builder</jv>.getSerializers().set(<jv>classes</jv>);
+ * </p>
+ *
+ * <ul class='seealso'>
+ * <li class='link'>{@doc jrs.Marshalling}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ Class<? extends Serializer>[] serializers() default {};
+
+ /**
+ * Optional summary for the exposed API.
+ *
+ * <p>
+ * This summary is used in the following locations:
+ * <ul class='spaced-list'>
+ * <li>
+ * The value returned by {@link Operation#getSummary()} in
the auto-generated swagger.
+ * <li>
+ * The <js>"$RS{operationSummary}"</js> variable.
+ * <li>
+ * The summary of the method in the Swagger page.
+ * </ul>
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * Corresponds to the swagger field
<c>/paths/{path}/{method}/summary</c>.
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$L{my.localized.variable}"</js>).
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ String summary() default "";
+
+ /**
+ * Provides swagger-specific metadata on this method.
+ *
+ * <p>
+ * Used to populate the auto-generated OPTIONS swagger documentation.
+ *
+ * <p>
+ * The format of this annotation is JSON when all individual parts are
concatenated.
+ * <br>The starting and ending <js>'{'</js>/<js>'}'</js> characters
around the entire value are optional.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <ja>@RestOptions</ja>(
+ * path=<js>"/{propertyName}"</js>,
+ *
+ * <jc>// Swagger info.</jc>
+ * swagger={
+ * <js>"parameters:["</js>,
+ *
<js>"{name:'propertyName',in:'path',description:'The system property
name.'},"</js>,
+ * <js>"{in:'body',description:'The new
system property value.'}"</js>,
+ * <js>"],"</js>,
+ * <js>"responses:{"</js>,
+ * <js>"302:
{headers:{Location:{description:'The root URL of this resource.'}}},"</js>,
+ * <js>"403: {description:'User is not an
admin.'}"</js>,
+ * <js>"}"</js>
+ * }
+ * )
+ * </p>
+ *
+ * <ul class='notes'>
+ * <li class='note'>
+ * The format is {@doc jd.Swagger}.
+ * <br>Multiple lines are concatenated with newlines.
+ * <li class='note'>
+ * The starting and ending <js>'{'</js>/<js>'}'</js>
characters around the entire value are optional.
+ * <li class='note'>
+ * These values are superimposed on top of any Swagger
JSON file present for the resource in the classpath.
+ * <li class='note'>
+ * Supports {@doc jrs.SvlVariables}
+ * (e.g. <js>"$L{my.localized.variable}"</js>).
+ * </ul>
+ *
+ * <ul class='seealso'>
+ * <li class='ja'>{@link OpSwagger}
+ * <li class='jc'>{@link SwaggerProvider}
+ * </ul>
+ *
+ * @return The annotation value.
+ */
+ OpSwagger swagger() default @OpSwagger;
+
+ /**
+ * REST method path.
+ *
+ * <p>
+ * Can be used to provide a shortened form for the {@link #path()}
value.
+ *
+ * <p>
+ * The following examples are considered equivalent.
+ * <p class='bjava'>
+ * <jc>// Normal form</jc>
+ * <ja>@RestOptions</ja>(path=<js>"/{propertyName}"</js>)
+ *
+ * <jc>// Shortened form</jc>
+ * <ja>@RestOptions</ja>(<js>"/{propertyName}"</js>)
+ * </p>
+ *
+ * @return The annotation value.
+ */
+ String value() default "";
+}
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
new file mode 100644
index 000000000..4cbfacf0f
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
@@ -0,0 +1,538 @@
+//
***************************************************************************************************************************
+// * 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.annotation;
+
+import static org.apache.juneau.http.HttpHeaders.*;
+import static org.apache.juneau.internal.ArrayUtils.*;
+import static org.apache.juneau.http.HttpParts.*;
+
+import java.lang.annotation.*;
+import java.nio.charset.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.reflect.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.converter.*;
+import org.apache.juneau.rest.guard.*;
+import org.apache.juneau.rest.httppart.*;
+import org.apache.juneau.rest.matcher.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+
+/**
+ * Utility classes and methods for the {@link RestOptions @RestOptions}
annotation.
+ *
+ * <ul class='seealso'>
+ * <li class='link'>{@doc jrs.RestOpAnnotatedMethods}
+ * <li class='extlink'>{@source}
+ * </ul>
+ */
+public class RestOptionsAnnotation {
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Static
+
//-----------------------------------------------------------------------------------------------------------------
+
+ /** Default value */
+ public static final RestOptions DEFAULT = create().build();
+
+ /**
+ * Instantiates a new builder for this class.
+ *
+ * @return A new builder object.
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Builder
+
//-----------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Builder class.
+ *
+ * <ul class='seealso'>
+ * <li class='jm'>{@link
org.apache.juneau.BeanContext.Builder#annotations(Annotation...)}
+ * </ul>
+ */
+ @SuppressWarnings("unchecked")
+ public static class Builder extends TargetedAnnotationMBuilder {
+
+ Class<? extends RestConverter>[] converters = new Class[0];
+ Class<? extends RestGuard>[] guards = new Class[0];
+ Class<? extends RestMatcher>[] matchers = new Class[0];
+ Class<? extends Encoder>[] encoders = new Class[0];
+ Class<? extends Serializer>[] serializers = new Class[0];
+ OpSwagger swagger = OpSwaggerAnnotation.DEFAULT;
+ String clientVersion="", debug="", defaultAccept="",
defaultCharset="", rolesDeclared="", roleGuard="", summary="", value="";
+ String[] defaultRequestQueryData={},
defaultRequestAttributes={}, defaultRequestHeaders={},
defaultResponseHeaders={}, description={}, path={}, produces={};
+
+ /**
+ * Constructor.
+ */
+ protected Builder() {
+ super(RestOptions.class);
+ }
+
+ /**
+ * Instantiates a new {@link RestOptions @RestOptions} object
initialized with this builder.
+ *
+ * @return A new {@link RestOptions @RestOptions} object.
+ */
+ public RestOptions build() {
+ return new Impl(this);
+ }
+
+ /**
+ * Sets the {@link RestOptions#clientVersion()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder clientVersion(String value) {
+ this.clientVersion = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#converters()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder converters(Class<? extends
RestConverter>...value) {
+ this.converters = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#debug()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder debug(String value) {
+ this.debug = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#defaultAccept()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder defaultAccept(String value) {
+ this.defaultAccept = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#defaultCharset()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder defaultCharset(String value) {
+ this.defaultCharset = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#defaultRequestQueryData()}
property on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder defaultRequestQueryData(String...value) {
+ this.defaultRequestQueryData = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#defaultRequestAttributes()}
property on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder defaultRequestAttributes(String...value) {
+ this.defaultRequestAttributes = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#defaultRequestHeaders()}
property on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder defaultRequestHeaders(String...value) {
+ this.defaultRequestHeaders = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#defaultResponseHeaders()}
property on this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder defaultResponseHeaders(String...value) {
+ this.defaultResponseHeaders = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#description()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder description(String...value) {
+ this.description = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#encoders()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder encoders(Class<? extends Encoder>...value) {
+ this.encoders = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#guards()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder guards(Class<? extends RestGuard>...value) {
+ this.guards = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#matchers()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder matchers(Class<? extends RestMatcher>...value) {
+ this.matchers = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#path()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder path(String...value) {
+ this.path = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#produces()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder produces(String...value) {
+ this.produces = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#roleGuard()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder roleGuard(String value) {
+ this.roleGuard = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#rolesDeclared()} property on
this annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder rolesDeclared(String value) {
+ this.rolesDeclared = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#serializers()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder serializers(Class<? extends Serializer>...value)
{
+ this.serializers = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#summary()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder summary(String value) {
+ this.summary = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#swagger()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder swagger(OpSwagger value) {
+ this.swagger = value;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RestOptions#value()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder value(String value) {
+ this.value = value;
+ return this;
+ }
+
+ // <FluentSetters>
+
+ @Override /* GENERATED - TargetedAnnotationBuilder */
+ public Builder on(String...values) {
+ super.on(values);
+ return this;
+ }
+
+ @Override /* GENERATED - TargetedAnnotationTMBuilder */
+ public Builder on(java.lang.reflect.Method...value) {
+ super.on(value);
+ return this;
+ }
+
+ // </FluentSetters>
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Implementation
+
//-----------------------------------------------------------------------------------------------------------------
+
+ private static class Impl extends TargetedAnnotationImpl implements
RestOptions {
+
+ private final Class<? extends RestConverter>[] converters;
+ private final Class<? extends RestGuard>[] guards;
+ private final Class<? extends RestMatcher>[] matchers;
+ private final Class<? extends Encoder>[] encoders;
+ private final Class<? extends Serializer>[] serializers;
+ private final OpSwagger swagger;
+ private final String clientVersion, debug, defaultAccept,
defaultCharset, rolesDeclared, roleGuard, summary, value;
+ private final String[] defaultRequestQueryData,
defaultRequestAttributes, defaultRequestHeaders, defaultResponseHeaders,
description, path, produces;
+
+ Impl(Builder b) {
+ super(b);
+ this.clientVersion = b.clientVersion;
+ this.converters = copyOf(b.converters);
+ this.debug = b.debug;
+ this.defaultAccept = b.defaultAccept;
+ this.defaultCharset = b.defaultCharset;
+ this.defaultRequestQueryData =
copyOf(b.defaultRequestQueryData);
+ this.defaultRequestAttributes =
copyOf(b.defaultRequestAttributes);
+ this.defaultRequestHeaders =
copyOf(b.defaultRequestHeaders);
+ this.defaultResponseHeaders =
copyOf(b.defaultResponseHeaders);
+ this.description = copyOf(b.description);
+ this.encoders = copyOf(b.encoders);
+ this.guards = copyOf(b.guards);
+ this.matchers = copyOf(b.matchers);
+ this.path = copyOf(b.path);
+ this.produces = copyOf(b.produces);
+ this.roleGuard = b.roleGuard;
+ this.rolesDeclared = b.rolesDeclared;
+ this.serializers = copyOf(b.serializers);
+ this.summary = b.summary;
+ this.swagger = b.swagger;
+ this.value = b.value;
+ postConstruct();
+ }
+
+ @Override /* RestOptions */
+ public String clientVersion() {
+ return clientVersion;
+ }
+
+ @Override /* RestOptions */
+ public Class<? extends RestConverter>[] converters() {
+ return converters;
+ }
+
+ @Override /* RestOptions */
+ public String debug() {
+ return debug;
+ }
+
+ @Override /* RestOptions */
+ public String defaultAccept() {
+ return defaultAccept;
+ }
+
+ @Override /* RestOptions */
+ public String defaultCharset() {
+ return defaultCharset;
+ }
+
+ @Override /* RestOptions */
+ public String[] defaultRequestQueryData() {
+ return defaultRequestQueryData;
+ }
+
+ @Override /* RestOptions */
+ public String[] defaultRequestAttributes() {
+ return defaultRequestAttributes;
+ }
+
+ @Override /* RestOptions */
+ public String[] defaultRequestHeaders() {
+ return defaultRequestHeaders;
+ }
+
+ @Override /* RestOptions */
+ public String[] defaultResponseHeaders() {
+ return defaultResponseHeaders;
+ }
+
+ @Override /* RestOptions */
+ public String[] description() {
+ return description;
+ }
+
+ @Override /* RestOptions */
+ public Class<? extends Encoder>[] encoders() {
+ return encoders;
+ }
+
+ @Override /* RestOptions */
+ public Class<? extends RestGuard>[] guards() {
+ return guards;
+ }
+
+ @Override /* RestOptions */
+ public Class<? extends RestMatcher>[] matchers() {
+ return matchers;
+ }
+
+ @Override /* RestOptions */
+ public String[] path() {
+ return path;
+ }
+
+ @Override /* RestOptions */
+ public String[] produces() {
+ return produces;
+ }
+
+ @Override /* RestOptions */
+ public String roleGuard() {
+ return roleGuard;
+ }
+
+ @Override /* RestOptions */
+ public String rolesDeclared() {
+ return rolesDeclared;
+ }
+
+ @Override /* RestOptions */
+ public Class<? extends Serializer>[] serializers() {
+ return serializers;
+ }
+
+ @Override /* RestOptions */
+ public String summary() {
+ return summary;
+ }
+
+ @Override /* RestOptions */
+ public OpSwagger swagger() {
+ return swagger;
+ }
+
+ @Override /* RestOptions */
+ public String value() {
+ return value;
+ }
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Appliers
+
//-----------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Applies {@link RestOptions} annotations to a {@link
org.apache.juneau.rest.RestOpContext.Builder}.
+ */
+ public static class RestOpContextApply extends
AnnotationApplier<RestOptions,RestOpContext.Builder> {
+
+ /**
+ * Constructor.
+ *
+ * @param vr The resolver for resolving values in annotations.
+ */
+ public RestOpContextApply(VarResolverSession vr) {
+ super(RestOptions.class, RestOpContext.Builder.class,
vr);
+ }
+
+ @Override
+ public void apply(AnnotationInfo<RestOptions> ai,
RestOpContext.Builder b) {
+ RestOptions a = ai.inner();
+
+ b.httpMethod("options");
+
+ classes(a.serializers()).ifPresent(x ->
b.serializers().set(x));
+ classes(a.encoders()).ifPresent(x ->
b.encoders().set(x));
+ stream(a.produces()).map(MediaType::of).forEach(x ->
b.produces(x));
+ stream(a.defaultRequestHeaders()).map(x ->
stringHeader(x)).forEach(x -> b.defaultRequestHeaders().setDefault(x));
+ stream(a.defaultResponseHeaders()).map(x ->
stringHeader(x)).forEach(x -> b.defaultResponseHeaders().setDefault(x));
+ stream(a.defaultRequestAttributes()).map(x ->
BasicNamedAttribute.ofPair(x)).forEach(x ->
b.defaultRequestAttributes().add(x));
+ stream(a.defaultRequestQueryData()).map(x ->
basicPart(x)).forEach(x -> b.defaultRequestQueryData().setDefault(x));
+ string(a.defaultAccept()).map(x ->
accept(x)).ifPresent(x -> b.defaultRequestHeaders().setDefault(x));
+ b.converters().append(a.converters());
+ b.guards().append(a.guards());
+ b.matchers().append(a.matchers());
+ string(a.clientVersion()).ifPresent(x ->
b.clientVersion(x));
+
string(a.defaultCharset()).map(Charset::forName).ifPresent(x ->
b.defaultCharset(x));
+ stream(a.path()).forEach(x -> b.path(x));
+ string(a.value()).ifPresent(x -> b.path(x));
+ cdl(a.rolesDeclared()).forEach(x -> b.rolesDeclared(x));
+ string(a.roleGuard()).ifPresent(x -> b.roleGuard(x));
+
string(a.debug()).map(Enablement::fromString).ifPresent(x -> b.debug(x));
+ }
+ }
+}
\ No newline at end of file
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
index 35b09b75e..66bba29cc 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
@@ -79,64 +79,64 @@ public class Remote_FormDataAnnotation_Test {
@Remote
public static interface A1 {
- @RemoteOp(path="a") String postX1(@FormData("x") int b);
- @RemoteOp(path="a") String postX2(@FormData("x") float b);
- @RemoteOp(path="a") String postX3(@FormData("x") Bean b);
- @RemoteOp(path="a") String postX4(@FormData("*") Bean b);
- @RemoteOp(path="a") String postX5(@FormData Bean b);
- @RemoteOp(path="a") String postX6(@FormData("x") Bean[] b);
- @RemoteOp(path="a") String postX7(@FormData("x")
@Schema(cf="uon") Bean[] b);
- @RemoteOp(path="a") String postX8(@FormData("x") List<Bean> b);
- @RemoteOp(path="a") String postX9(@FormData("x")
@Schema(cf="uon") List<Bean> b);
- @RemoteOp(path="a") String postX10(@FormData("x")
Map<String,Bean> b);
- @RemoteOp(path="a") String postX11(@FormData("*")
Map<String,Bean> b);
- @RemoteOp(path="a") String postX12(@FormData Map<String,Bean>
b);
- @RemoteOp(path="a") String postX13(@FormData("x")
@Schema(f="uon") Map<String,Bean> b);
- @RemoteOp(path="a") String postX14(@FormData @Schema(f="uon")
Map<String,Bean> b);
- @RemoteOp(path="a") String postX15(@FormData("*") Reader b);
- @RemoteOp(path="a") String postX16(@FormData Reader b);
- @RemoteOp(path="a") String postX17(@FormData("*") InputStream
b);
- @RemoteOp(path="a") String postX18(@FormData InputStream b);
- @RemoteOp(path="a") String postX19(@FormData("*") PartList b);
- @RemoteOp(path="a") String postX20(@FormData PartList b);
- @RemoteOp(path="a") String postX21(@FormData NameValuePair b);
- @RemoteOp(path="a") String postX22(@FormData String b);
- @RemoteOp(path="a") String postX23(@FormData InputStream b);
- @RemoteOp(path="a") String postX24(@FormData Reader b);
- @RemoteOp(path="a") String postX25(@FormData Bean2 b);
- @RemoteOp(path="a") String postX26(@FormData
List<NameValuePair> b);
+ @RemotePost(path="a") String x1(@FormData("x") int b);
+ @RemotePost(path="a") String x2(@FormData("x") float b);
+ @RemotePost(path="a") String x3(@FormData("x") Bean b);
+ @RemotePost(path="a") String x4(@FormData("*") Bean b);
+ @RemotePost(path="a") String x5(@FormData Bean b);
+ @RemotePost(path="a") String x6(@FormData("x") Bean[] b);
+ @RemotePost(path="a") String x7(@FormData("x")
@Schema(cf="uon") Bean[] b);
+ @RemotePost(path="a") String x8(@FormData("x") List<Bean> b);
+ @RemotePost(path="a") String x9(@FormData("x")
@Schema(cf="uon") List<Bean> b);
+ @RemotePost(path="a") String x10(@FormData("x")
Map<String,Bean> b);
+ @RemotePost(path="a") String x11(@FormData("*")
Map<String,Bean> b);
+ @RemotePost(path="a") String x12(@FormData Map<String,Bean> b);
+ @RemotePost(path="a") String x13(@FormData("x")
@Schema(f="uon") Map<String,Bean> b);
+ @RemotePost(path="a") String x14(@FormData @Schema(f="uon")
Map<String,Bean> b);
+ @RemotePost(path="a") String x15(@FormData("*") Reader b);
+ @RemotePost(path="a") String x16(@FormData Reader b);
+ @RemotePost(path="a") String x17(@FormData("*") InputStream b);
+ @RemotePost(path="a") String x18(@FormData InputStream b);
+ @RemotePost(path="a") String x19(@FormData("*") PartList b);
+ @RemotePost(path="a") String x20(@FormData PartList b);
+ @RemotePost(path="a") String x21(@FormData NameValuePair b);
+ @RemotePost(path="a") String x22(@FormData String b);
+ @RemotePost(path="a") String x23(@FormData InputStream b);
+ @RemotePost(path="a") String x24(@FormData Reader b);
+ @RemotePost(path="a") String x25(@FormData Bean2 b);
+ @RemotePost(path="a") String x26(@FormData List<NameValuePair>
b);
}
@Test
public void a01_objectTypes() throws Exception {
A1 x = MockRestClient.build(A.class).getRemote(A1.class);
- assertEquals("{x:'1'}",x.postX1(1));
- assertEquals("{x:'1.0'}",x.postX2(1));
- assertEquals("{x:'f=1'}",x.postX3(Bean.create()));
- assertEquals("{f:'1'}",x.postX4(Bean.create()));
- assertEquals("{f:'1'}",x.postX5(Bean.create()));
- assertEquals("{x:'f=1,f=1'}",x.postX6(new
Bean[]{Bean.create(),Bean.create()}));
- assertEquals("{x:'@((f=1),(f=1))'}",x.postX7(new
Bean[]{Bean.create(),Bean.create()}));
-
assertEquals("{x:'f=1,f=1'}",x.postX8(alist(Bean.create(),Bean.create())));
-
assertEquals("{x:'@((f=1),(f=1))'}",x.postX9(alist(Bean.create(),Bean.create())));
-
assertEquals("{x:'k1=f\\\\=1'}",x.postX10(map("k1",Bean.create())));
- assertEquals("{k1:'f=1'}",x.postX11(map("k1",Bean.create())));
- assertEquals("{k1:'f=1'}",x.postX12(map("k1",Bean.create())));
-
assertEquals("{x:'k1=f\\\\=1'}",x.postX13(map("k1",Bean.create())));
- assertEquals("{k1:'f=1'}",x.postX14(map("k1",Bean.create())));
- assertEquals("{x:'1'}",x.postX15(reader("x=1")));
- assertEquals("{x:'1'}",x.postX16(reader("x=1")));
- assertEquals("{x:'1'}",x.postX17(inputStream("x=1")));
- assertEquals("{x:'1'}",x.postX18(inputStream("x=1")));
- assertEquals("{foo:'bar'}",x.postX19(parts("foo","bar")));
- assertEquals("{foo:'bar'}",x.postX20(parts("foo","bar")));
- assertEquals("{foo:'bar'}",x.postX21(part("foo","bar")));
- assertEquals("{foo:'bar'}",x.postX22("foo=bar"));
- assertEquals("{}",x.postX22(null));
- assertEquals("{foo:'bar'}",x.postX23(inputStream("foo=bar")));
- assertEquals("{foo:'bar'}",x.postX24(reader("foo=bar")));
- assertEquals("{f:'1'}",x.postX25(Bean2.create()));
- assertEquals("{foo:'bar'}",x.postX26(alist(part("foo","bar"))));
+ assertEquals("{x:'1'}",x.x1(1));
+ assertEquals("{x:'1.0'}",x.x2(1));
+ assertEquals("{x:'f=1'}",x.x3(Bean.create()));
+ assertEquals("{f:'1'}",x.x4(Bean.create()));
+ assertEquals("{f:'1'}",x.x5(Bean.create()));
+ assertEquals("{x:'f=1,f=1'}",x.x6(new
Bean[]{Bean.create(),Bean.create()}));
+ assertEquals("{x:'@((f=1),(f=1))'}",x.x7(new
Bean[]{Bean.create(),Bean.create()}));
+
assertEquals("{x:'f=1,f=1'}",x.x8(alist(Bean.create(),Bean.create())));
+
assertEquals("{x:'@((f=1),(f=1))'}",x.x9(alist(Bean.create(),Bean.create())));
+ assertEquals("{x:'k1=f\\\\=1'}",x.x10(map("k1",Bean.create())));
+ assertEquals("{k1:'f=1'}",x.x11(map("k1",Bean.create())));
+ assertEquals("{k1:'f=1'}",x.x12(map("k1",Bean.create())));
+ assertEquals("{x:'k1=f\\\\=1'}",x.x13(map("k1",Bean.create())));
+ assertEquals("{k1:'f=1'}",x.x14(map("k1",Bean.create())));
+ assertEquals("{x:'1'}",x.x15(reader("x=1")));
+ assertEquals("{x:'1'}",x.x16(reader("x=1")));
+ assertEquals("{x:'1'}",x.x17(inputStream("x=1")));
+ assertEquals("{x:'1'}",x.x18(inputStream("x=1")));
+ assertEquals("{foo:'bar'}",x.x19(parts("foo","bar")));
+ assertEquals("{foo:'bar'}",x.x20(parts("foo","bar")));
+ assertEquals("{foo:'bar'}",x.x21(part("foo","bar")));
+ assertEquals("{foo:'bar'}",x.x22("foo=bar"));
+ assertEquals("{}",x.x22(null));
+ assertEquals("{foo:'bar'}",x.x23(inputStream("foo=bar")));
+ assertEquals("{foo:'bar'}",x.x24(reader("foo=bar")));
+ assertEquals("{f:'1'}",x.x25(Bean2.create()));
+ assertEquals("{foo:'bar'}",x.x26(alist(part("foo","bar"))));
}
//-----------------------------------------------------------------------------------------------------------------
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_Test.java
index 7c8675711..2f2655e8d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_Test.java
@@ -27,6 +27,7 @@ import org.apache.juneau.rest.config.*;
import org.apache.juneau.rest.mock.*;
import org.apache.juneau.rest.servlet.*;
import org.apache.juneau.http.*;
+import org.apache.juneau.http.annotation.*;
import org.apache.juneau.http.header.*;
import org.apache.juneau.marshaller.*;
import org.junit.*;
@@ -622,6 +623,108 @@ public class Remote_Test {
assertThrown(()->client(G.class).header("Check","Foo").build().getRemote(G1.class)).isType(RemoteMetadataException.class).asMessage().isContains("Invalid
value");
}
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Method detection
+
//-----------------------------------------------------------------------------------------------------------------
+
+ @Rest
+ public static class H extends BasicRestObject {
+ @RestOp(method="*", path="/*")
+ public String echoMethod(@Method String method, @Path("/*")
String path) {
+ return method + " " + path;
+ }
+ }
+
+ @Remote
+ public static interface H1 {
+ @RemoteOp(method="get") String a1();
+ @RemoteOp(method="put") String a2();
+ @RemoteOp(method="post") String a3();
+ @RemoteOp(method="patch") String a4();
+ @RemoteOp(method="delete") String a5();
+ @RemoteOp(method="options") String a6();
+ @RemoteGet String a11();
+ @RemotePut String a12();
+ @RemotePost String a13();
+ @RemotePatch String a14();
+ @RemoteDelete String a15();
+ @RemoteOp String getA21();
+ @RemoteOp String putA22();
+ @RemoteOp String postA23();
+ @RemoteOp String patchA24();
+ @RemoteOp String deleteA25();
+ @RemoteOp String optionsA26();
+ @RemoteGet("/a31x") String a31();
+ @RemotePut("/a32x") String a32();
+ @RemotePost("/a33x") String a33();
+ @RemotePatch("/a34x") String a34();
+ @RemoteDelete("/a35x") String a35();
+ @RemoteOp("GET /a41x") String a41();
+ @RemoteOp("PUT /a42x") String a42();
+ @RemoteOp("POST /a43x") String a43();
+ @RemoteOp("PATCH /a44x") String a44();
+ @RemoteOp("DELETE /a45x") String a45();
+ @RemoteOp("OPTIONS /a46x") String a46();
+ @RemoteGet("a51x") String a51();
+ @RemotePut("a52x") String a52();
+ @RemotePost("a53x") String a53();
+ @RemotePatch("a54x") String a54();
+ @RemoteDelete("a55x") String a55();
+ @RemoteOp("GET a61x") String a61();
+ @RemoteOp("PUT a62x") String a62();
+ @RemoteOp("POST a63x") String a63();
+ @RemoteOp("PATCH a64x") String a64();
+ @RemoteOp("DELETE a65x") String a65();
+ @RemoteOp("OPTIONS a66x") String a66();
+ }
+
+
+ @Test
+ public void h01_methodDetection() throws Exception {
+
+ H1 x = client(H.class).build().getRemote(H1.class);
+ assertEquals("GET a1", x.a1());
+ assertEquals("PUT a2", x.a2());
+ assertEquals("POST a3", x.a3());
+ assertEquals("PATCH a4", x.a4());
+ assertEquals("DELETE a5", x.a5());
+ assertEquals("OPTIONS a6", x.a6());
+ assertEquals("GET a11", x.a11());
+ assertEquals("PUT a12", x.a12());
+ assertEquals("POST a13", x.a13());
+ assertEquals("PATCH a14", x.a14());
+ assertEquals("DELETE a15", x.a15());
+ assertEquals("GET a21", x.getA21());
+ assertEquals("PUT a22", x.putA22());
+ assertEquals("POST a23", x.postA23());
+ assertEquals("PATCH a24", x.patchA24());
+ assertEquals("DELETE a25", x.deleteA25());
+ assertEquals("OPTIONS a26", x.optionsA26());
+ assertEquals("GET a31x", x.a31());
+ assertEquals("PUT a32x", x.a32());
+ assertEquals("POST a33x", x.a33());
+ assertEquals("PATCH a34x", x.a34());
+ assertEquals("DELETE a35x", x.a35());
+ assertEquals("GET a41x", x.a41());
+ assertEquals("PUT a42x", x.a42());
+ assertEquals("POST a43x", x.a43());
+ assertEquals("PATCH a44x", x.a44());
+ assertEquals("DELETE a45x", x.a45());
+ assertEquals("OPTIONS a46x", x.a46());
+ assertEquals("GET a51x", x.a51());
+ assertEquals("PUT a52x", x.a52());
+ assertEquals("POST a53x", x.a53());
+ assertEquals("PATCH a54x", x.a54());
+ assertEquals("DELETE a55x", x.a55());
+ assertEquals("GET a61x", x.a61());
+ assertEquals("PUT a62x", x.a62());
+ assertEquals("POST a63x", x.a63());
+ assertEquals("PATCH a64x", x.a64());
+ assertEquals("DELETE a65x", x.a65());
+ assertEquals("OPTIONS a66x", x.a66());
+ }
+
//-----------------------------------------------------------------------------------------------------------------
// Helper methods.
//-----------------------------------------------------------------------------------------------------------------