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>&lt;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 
&amp;&amp; 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* &amp; *oo"</js> - Multiple AND'ed 
arguments, ampersand syntax.
+        *                      <li><js>"fo* &amp;&amp; *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 
&amp;&amp; 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.
        
//-----------------------------------------------------------------------------------------------------------------

Reply via email to