You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2018/07/16 13:21:18 UTC
[juneau] branch master updated: Add OpenAPI support to remoteable
interfaces.
This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new a2d9508 Add OpenAPI support to remoteable interfaces.
a2d9508 is described below
commit a2d95084b7eb836a49b97c4f3432cfc659b458d9
Author: JamesBognar <ja...@apache.org>
AuthorDate: Mon Jul 16 09:21:05 2018 -0400
Add OpenAPI support to remoteable interfaces.
---
.../juneau/httppart/HttpPartSchemaTest_Body.java | 5 +-
.../java/org/apache/juneau/BeanPropertyMeta.java | 2 +
.../org/apache/juneau/http/annotation/Body.java | 101 ++-----
.../org/apache/juneau/http/annotation/Path.java | 11 +
.../org/apache/juneau/httppart/HttpPartSchema.java | 34 +++
.../juneau/httppart/HttpPartSchemaBuilder.java | 26 +-
.../apache/juneau/internal/ReflectionUtils.java | 29 +++
.../jsonschema/JsonSchemaSerializerSession.java | 8 +-
.../apache/juneau/remoteable/RemoteMethodArg.java | 115 ++++++--
.../juneau/remoteable/RemoteMethodBeanArg.java | 112 ++++++++
...emoteMethodArg.java => RemoteMethodReturn.java} | 81 ++++--
.../juneau/remoteable/RemoteableMethodMeta.java | 98 ++++---
.../org/apache/juneau/remoteable/ReturnValue.java | 5 +-
.../org/apache/juneau/svl/VarResolverSession.java | 4 +-
juneau-doc/src/main/javadoc/overview.html | 289 +++++++++++++++++++--
.../rest/test/client/RequestBeanProxyTest.java | 72 ++---
.../rest/test/client/ThirdPartyProxyTest.java | 16 +-
.../apache/juneau/rest/client/NameValuePairs.java | 2 +-
.../org/apache/juneau/rest/client/RestCall.java | 137 +++++++---
.../juneau/rest/client/RestCallException.java | 4 +-
.../org/apache/juneau/rest/client/RestClient.java | 122 +++++----
.../juneau/rest/client/RestClientBuilder.java | 44 +++-
.../rest/client/SerializedNameValuePair.java | 7 +-
.../org/apache/juneau/rest/BasicRestLogger.java | 5 +
.../java/org/apache/juneau/rest/RestContext.java | 3 +
.../java/org/apache/juneau/rest/RestLogger.java | 7 +
26 files changed, 985 insertions(+), 354 deletions(-)
diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/httppart/HttpPartSchemaTest_Body.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/httppart/HttpPartSchemaTest_Body.java
index 4b57053..088424f 100644
--- a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/httppart/HttpPartSchemaTest_Body.java
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/httppart/HttpPartSchemaTest_Body.java
@@ -78,7 +78,6 @@ public class HttpPartSchemaTest_Body {
public static class A04 {
public void a(
@Body(
- required=false,
description={"b3","b3"},
schema=@Schema($ref="c3"),
example="f2",
@@ -92,8 +91,8 @@ public class HttpPartSchemaTest_Body {
@Test
public void a04_basic_onParameterAndClass() throws Exception {
HttpPartSchema s = HttpPartSchema.create().apply(Body.class, A04.class.getMethod("a", A02.class), 0).noValidate().build();
- assertNull(s.getRequired());
- assertObjectEquals("{description:'b3\\nb3',example:'f2',schema:{'$ref':'c3'},_value:'{g2:true}'}", s.getApi());
+ assertTrue(s.getRequired());
+ assertObjectEquals("{description:'b3\\nb3',example:'f2',required:true,schema:{'$ref':'c3'},_value:'{g2:true}'}", s.getApi());
}
@Body(
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
index b6c7770..12ad597 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
@@ -1079,6 +1079,8 @@ public final class BeanPropertyMeta {
t = getMethodAnnotation(a, setter);
if (t == null && extraKeys != null)
t = getMethodAnnotation(a, extraKeys);
+ if (t == null)
+ t = ReflectionUtils.getAnnotation(a, typeMeta.getInnerClass());
return t;
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Body.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Body.java
index d281c8c..fa2b005 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Body.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Body.java
@@ -17,8 +17,6 @@ import static java.lang.annotation.RetentionPolicy.*;
import java.io.*;
import java.lang.annotation.*;
-import java.nio.charset.*;
-import java.util.logging.*;
import org.apache.juneau.*;
import org.apache.juneau.httppart.*;
@@ -45,6 +43,15 @@ import org.apache.juneau.serializer.*;
* <p>
* On server-side REST, this annotation can be applied to method parameters or parameter classes to identify them as the body of an HTTP request.
*
+ * <p>
+ * This annotation can be applied to the following:
+ * <ul class='spaced-list'>
+ * <li>
+ * Parameters on a <ja>@RestMethod</ja>-annotated method.
+ * <li>
+ * POJO classes used as parameters on a <ja>@RestMethod</ja>-annotated method.
+ * </ul>
+ *
* <h5 class='section'>Examples:</h5>
* <p class='bcode w800'>
* <jc>// Used on parameter</jc>
@@ -70,90 +77,10 @@ import org.apache.juneau.serializer.*;
* }
* </p>
*
- * <p>
- * This annotation is also used for supplying swagger information about the body of the request.
- *
- * <h5 class='section'>Examples:</h5>
- * <p class='bcode w800'>
- * <jc>// Normal</jc>
- * <ja>@Body</ja>(
- * description=<js>"Pet object to add to the store"</js>,
- * required=<js>"true"</js>,
- * example=<js>"{name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']}"</js>
- * )
- * </p>
- * <p class='bcode w800'>
- * <jc>// Free-form</jc>
- * <ja>@Body</ja>({
- * <js>"description: 'Pet object to add to the store',"</js>,
- * <js>"required: true,"</js>,
- * <js>"example: {name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']},"</js>
- * <js>"x-extra: 'extra field'"</js>
- * })
- * </p>
- *
- * <p>
- * This is used to populate the auto-generated Swagger documentation and UI.
- *
- * <p>
- * This annotation can be applied to the following:
- * <ul class='spaced-list'>
- * <li>
- * Parameters on a <ja>@RestMethod</ja>-annotated method.
- * <li>
- * POJO classes used as parameters on a <ja>@RestMethod</ja>-annotated method.
- * </ul>
- *
- * <p>
- * Any of the following types can be used (matched in the specified order):
- * <ol class='spaced-list'>
- * <li>
- * {@link Reader}
- * <br><ja>@Body</ja> annotation is optional (it's inferred from the class type).
- * <br><code>Content-Type</code> is always ignored.
- * <li>
- * {@link InputStream}
- * <br><ja>@Body</ja> annotation is optional (it's inferred from the class type).
- * <br><code>Content-Type</code> is always ignored.
- * <li>
- * Any <a class='doclink' href='../../../../../overview-summary.html#juneau-marshall.PojoCategories'>parsable</a> POJO type.
- * <br><code>Content-Type</code> is required to identify correct parser.
- * <li>
- * Objects convertible from {@link Reader} by having one of the following non-deprecated methods:
- * <ul>
- * <li><code><jk>public</jk> T(Reader in) {...}</code>
- * <li><code><jk>public static</jk> T <jsm>create</jsm>(Reader in) {...}</code>
- * <li><code><jk>public static</jk> T <jsm>fromReader</jsm>(Reader in) {...}</code>
- * </ul>
- * <br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO.
- * <li>
- * Objects convertible from {@link InputStream} by having one of the following non-deprecated methods:
- * <ul>
- * <li><code><jk>public</jk> T(InputStream in) {...}</code>
- * <li><code><jk>public static</jk> T <jsm>create</jsm>(InputStream in) {...}</code>
- * <li><code><jk>public static</jk> T <jsm>fromInputStream</jsm>(InputStream in) {...}</code>
- * </ul>
- * <br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO.
- * <li>
- * Objects convertible from {@link String} (including <code>String</code> itself) by having one of the following non-deprecated methods:
- * <ul>
- * <li><code><jk>public</jk> T(String in) {...}</code> (e.g. {@link Integer}, {@link Boolean})
- * <li><code><jk>public static</jk> T <jsm>create</jsm>(String in) {...}</code>
- * <li><code><jk>public static</jk> T <jsm>fromString</jsm>(String in) {...}</code>
- * <li><code><jk>public static</jk> T <jsm>fromValue</jsm>(String in) {...}</code>
- * <li><code><jk>public static</jk> T <jsm>valueOf</jsm>(String in) {...}</code> (e.g. enums)
- * <li><code><jk>public static</jk> T <jsm>parse</jsm>(String in) {...}</code> (e.g. {@link Level})
- * <li><code><jk>public static</jk> T <jsm>parseString</jsm>(String in) {...}</code>
- * <li><code><jk>public static</jk> T <jsm>forName</jsm>(String in) {...}</code> (e.g. {@link Class}, {@link Charset})
- * <li><code><jk>public static</jk> T <jsm>forString</jsm>(String in) {...}</code>
- * </ul>
- * <br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO.
- * </ol>
- *
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
- * Annotation values are coalesced from multiple sources in the following order of precedence:
+ * Swagger values are coalesced from multiple sources in the following order of precedence:
* <ol>
* <li><ja>@Body</ja> annotation on parameter.
* <li><ja>@Body</ja> annotation on parameter class.
@@ -225,7 +152,7 @@ import org.apache.juneau.serializer.*;
*
* <h5 class='section'>See Also:</h5>
* <ul class='doctree'>
- * <li class='link'><a class='doclink' href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies'>Overview > juneau-rest-client > Interface Proxies Against 3rd-party REST Interfaces</a>
+ * <li class='link'><a class='doclink' href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies.Body'>Overview > juneau-rest-client > Interface Proxies Against 3rd-party REST Interfaces > Body</a>
* </ul>
*/
@Documented
@@ -233,6 +160,7 @@ import org.apache.juneau.serializer.*;
@Retention(RUNTIME)
@Inherited
public @interface Body {
+
//=================================================================================================================
// Attributes common to all Swagger Parameter objects
//=================================================================================================================
@@ -562,6 +490,11 @@ public @interface Body {
String[] api() default {};
/**
+ * TODO
+ */
+ Class<? extends HttpPartSerializer> serializer() default HttpPartSerializer.Null.class;
+
+ /**
* Specifies the {@link HttpPartParser} class used for parsing values from strings.
*
* <p>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
index 7a17097..348e3ad 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
@@ -399,6 +399,17 @@ public @interface Path {
String format() default "";
/**
+ * <mk>allowEmptyValue</mk> field of the Swagger <a class="doclink" href="https://swagger.io/specification/v2/#parameterObject">Parameter</a> object.
+ *
+ * <p>
+ * Sets the ability to pass empty-valued heaver values.
+ *
+ * <p>
+ * <b>Note:</b> This is technically only valid for either query or formData parameters, but support is provided anyway for backwards compatability.
+ */
+ boolean allowEmptyValue() default false;
+
+ /**
* <mk>items</mk> field of the Swagger <a class="doclink" href="https://swagger.io/specification/v2/#parameterObject">Parameter</a> object.
*
* <p>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
index c4dc13a..5dd9ff8 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
@@ -115,6 +115,40 @@ public class HttpPartSchema {
}
/**
+ * Finds the schema information for the specified method return.
+ *
+ * <p>
+ * This method will gather all the schema information from the annotations at the following locations:
+ * <ul>
+ * <li>The method.
+ * <li>The method return class.
+ * <li>The method return parent classes and interfaces.
+ * </ul>
+ *
+ * @param c
+ * The annotation to look for.
+ * <br>Valid values:
+ * <ul>
+ * <li>{@link Body}
+ * <li>{@link Header}
+ * <li>{@link Query}
+ * <li>{@link FormData}
+ * <li>{@link Path}
+ * <li>{@link Response}
+ * <li>{@link ResponseHeader}
+ * <li>{@link ResponseStatus}
+ * <li>{@link HasQuery}
+ * <li>{@link HasFormData}
+ * </ul>
+ * @param m
+ * The Java method with the return type being checked.
+ * @return The schema information about the parameter.
+ */
+ public static HttpPartSchema create(Class<? extends Annotation> c, Method m) {
+ return create().apply(c, m).build();
+ }
+
+ /**
* Finds the schema information for the specified class.
*
* <p>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
index d3479cc..16c0dcf 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
@@ -63,10 +63,18 @@ public class HttpPartSchemaBuilder {
}
HttpPartSchemaBuilder apply(Class<? extends Annotation> c, Method m, int index) {
+ apply(c, m.getGenericParameterTypes()[index]);
for (Annotation a : m.getParameterAnnotations()[index])
if (c.isInstance(a))
- return apply(a);
- apply(c, m.getGenericParameterTypes()[index]);
+ apply(a);
+ return this;
+ }
+
+ HttpPartSchemaBuilder apply(Class<? extends Annotation> c, Method m) {
+ apply(c, m.getGenericReturnType());
+ Annotation a = m.getAnnotation(c);
+ if (a != null)
+ return apply(a);
return this;
}
@@ -77,7 +85,13 @@ public class HttpPartSchemaBuilder {
return this;
}
- HttpPartSchemaBuilder apply(Annotation a) {
+ /**
+ * Apply the specified annotation to this schema.
+ *
+ * @param a The annotation to apply.
+ * @return This object (for method chaining).
+ */
+ public HttpPartSchemaBuilder apply(Annotation a) {
if (a instanceof Body)
apply((Body)a);
else if (a instanceof Header)
@@ -105,6 +119,7 @@ public class HttpPartSchemaBuilder {
api = AnnotationUtils.merge(api, a);
required(HttpPartSchema.toBoolean(a.required()));
allowEmptyValue(HttpPartSchema.toBoolean(! a.required()));
+ serializer(a.serializer());
parser(a.parser());
apply(a.schema());
return this;
@@ -238,6 +253,7 @@ public class HttpPartSchemaBuilder {
type(a.type());
format(a.format());
items(a.items());
+ allowEmptyValue(HttpPartSchema.toBoolean(a.allowEmptyValue()));
collectionFormat(a.collectionFormat());
maximum(HttpPartSchema.toNumber(a.maximum()));
exclusiveMaximum(HttpPartSchema.toBoolean(a.exclusiveMaximum()));
@@ -1332,7 +1348,7 @@ public class HttpPartSchemaBuilder {
* @return This object (for method chaining).
*/
public HttpPartSchemaBuilder serializer(Class<? extends HttpPartSerializer> value) {
- if (serializer != null && serializer != HttpPartSerializer.Null.class)
+ if (value != null && value != HttpPartSerializer.Null.class)
serializer = value;
return this;
}
@@ -1346,7 +1362,7 @@ public class HttpPartSchemaBuilder {
* @return This object (for method chaining).
*/
public HttpPartSchemaBuilder parser(Class<? extends HttpPartParser> value) {
- if (parser != null && parser != HttpPartParser.Null.class)
+ if (value != null && value != HttpPartParser.Null.class)
parser = value;
return this;
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReflectionUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReflectionUtils.java
index 2cec856..0464c3f 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReflectionUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReflectionUtils.java
@@ -37,6 +37,17 @@ public final class ReflectionUtils {
}
/**
+ * Returns <jk>true</jk> if the {@link #getAnnotation(Class, Method)} returns a value.
+ *
+ * @param a The annotation to check for.
+ * @param m The method to check.
+ * @return <jk>true</jk> if the {@link #getAnnotation(Class, Method)} returns a value.
+ */
+ public static boolean hasAnnotation(Class<? extends Annotation> a, Method m) {
+ return getAnnotation(a, m) != null;
+ }
+
+ /**
* Returns the specified annotation if it exists on the specified parameter or parameter type class.
*
* @param a The annotation to check for.
@@ -56,6 +67,24 @@ public final class ReflectionUtils {
}
/**
+ * Returns the specified annotation if it exists on the specified method or return type class.
+ *
+ * @param a The annotation to check for.
+ * @param m The method to check.
+ * @return <jk>true</jk> if the {@link #getAnnotation(Class, Method, int)} returns a value.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends Annotation> T getAnnotation(Class<T> a, Method m) {
+ for (Annotation a2 : m.getAnnotations())
+ if (a.isInstance(a2))
+ return (T)a2;
+ Type t = m.getGenericReturnType();
+ if (t instanceof Class)
+ return getAnnotation(a, (Class<?>)t);
+ return null;
+ }
+
+ /**
* Similar to {@link Class#getAnnotation(Class)} except also searches annotations on interfaces.
*
* @param <T> The annotation class type.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaSerializerSession.java
index c6f19db..e4fb8f8 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaSerializerSession.java
@@ -109,12 +109,16 @@ public class JsonSchemaSerializerSession extends JsonSerializerSession {
descriptionAdded = false;
}
- if (useDef && defs.containsKey(getBeanDefId(sType)))
+ if (useDef && defs.containsKey(getBeanDefId(sType))) {
+ pop();
return new ObjectMap().append("$ref", getBeanDefUri(sType));
+ }
ObjectMap ds = getDefaultSchemas().get(sType.getInnerClass().getName());
- if (ds != null && ds.containsKey("type"))
+ if (ds != null && ds.containsKey("type")) {
+ pop();
return out.appendAll(ds);
+ }
JsonSchemaClassMeta jscm = null;
if (pojoSwap != null && pojoSwap.getClass().getAnnotation(JsonSchema.class) != null)
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
index f188334..b715b85 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
@@ -13,9 +13,14 @@
package org.apache.juneau.remoteable;
import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.ReflectionUtils.*;
+import static org.apache.juneau.httppart.HttpPartType.*;
+import java.lang.reflect.*;
+
+import org.apache.juneau.http.annotation.*;
import org.apache.juneau.httppart.*;
-import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.internal.*;
/**
* Represents the metadata about an annotated argument of a method on a remote proxy interface.
@@ -25,35 +30,99 @@ import org.apache.juneau.urlencoding.*;
* <li class='link'><a class='doclink' href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies'>Overview > juneau-rest-client > Interface Proxies Against 3rd-party REST Interfaces</a>
* </ul>
*/
-public class RemoteMethodArg {
+public final class RemoteMethodArg {
+
+ private final int index;
+ private final HttpPartType partType;
+ private final HttpPartSerializer serializer;
+ private final HttpPartSchema schema;
+ private final String name;
+ private final boolean skipIfEmpty;
- /** The argument name. Can be blank. */
- public final String name;
+ RemoteMethodArg(int index, HttpPartType partType, HttpPartSchema schema) {
+ this.index = index;
+ this.partType = partType;
+ this.serializer = newInstance(HttpPartSerializer.class, schema == null ? null : schema.getSerializer());
+ this.schema = schema;
+ this.name = schema == null ? null : schema.getName();
+ this.skipIfEmpty = schema == null || schema.getSkipIfEmpty() == null ? false : schema.getSkipIfEmpty();
+ }
- /** The zero-based index of the argument on the Java method. */
- public final int index;
+ RemoteMethodArg(HttpPartType partType, HttpPartSchema schema, String defaultName) {
+ this.index = -1;
+ this.partType = partType;
+ this.serializer = newInstance(HttpPartSerializer.class, schema == null ? null : schema.getSerializer());
+ this.schema = schema;
+ this.name = StringUtils.firstNonEmpty(schema == null ? null : schema.getName(), defaultName);
+ this.skipIfEmpty = schema == null || schema.getSkipIfEmpty() == null ? false : schema.getSkipIfEmpty();
+ }
- /** The value is skipped if it's null/empty. */
- public final boolean skipIfNE;
+ /**
+ * Returns the name of the HTTP part.
+ *
+ * @return The name of the HTTP part.
+ */
+ public String getName() {
+ return name;
+ }
- /** The serializer used for converting objects to strings. */
- public final HttpPartSerializer serializer;
+ /**
+ * Returns whether the <code>skipIfEmpty</code> flag was found in the schema.
+ *
+ * @return <jk>true</jk> if the <code>skipIfEmpty</code> flag was found in the schema.
+ */
+ public boolean isSkipIfEmpty() {
+ return skipIfEmpty;
+ }
/**
- * Constructor.
+ * Returns the method argument index.
*
- * @param name The argument name pulled from name().
- * @param name2 The argument name pulled from value().
- * @param index The zero-based index of the argument on the Java method.
- * @param skipIfNE The value is skipped if it's null/empty.
- * @param serializer
- * The class to use for serializing headers, query parameters, form-data parameters, and path variables.
- * If {@link UrlEncodingSerializer}, then the url-encoding serializer defined on the client will be used.
+ * @return The method argument index.
*/
- protected RemoteMethodArg(String name, String name2, int index, boolean skipIfNE, Class<? extends HttpPartSerializer> serializer) {
- this.name = name.isEmpty() ? name2 : name;
- this.index = index;
- this.skipIfNE = skipIfNE;
- this.serializer = newInstance(HttpPartSerializer.class, serializer);
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Returns the HTTP part type.
+ *
+ * @return The HTTP part type. Never <jk>null</jk>.
+ */
+ public HttpPartType getPartType() {
+ return partType;
+ }
+
+ /**
+ * Returns the HTTP part serializer to use for serializing this part.
+ *
+ * @return The HTTP part serializer, or <jk>null</jk> if not specified.
+ */
+ public HttpPartSerializer getSerializer() {
+ return serializer;
+ }
+
+ /**
+ * Returns the HTTP part schema information about this part.
+ *
+ * @return The HTTP part schema information, or <jk>null</jk> if not found.
+ */
+ public HttpPartSchema getSchema() {
+ return schema;
+ }
+
+ static RemoteMethodArg create(Method m, int i) {
+ if (hasAnnotation(Header.class, m, i)) {
+ return new RemoteMethodArg(i, HEADER, HttpPartSchema.create(Header.class, m, i));
+ } else if (hasAnnotation(Query.class, m, i)) {
+ return new RemoteMethodArg(i, QUERY, HttpPartSchema.create(Query.class, m, i));
+ } else if (hasAnnotation(FormData.class, m, i)) {
+ return new RemoteMethodArg(i, FORMDATA, HttpPartSchema.create(FormData.class, m, i));
+ } else if (hasAnnotation(Path.class, m, i)) {
+ return new RemoteMethodArg(i, PATH, HttpPartSchema.create(Path.class, m, i));
+ } else if (hasAnnotation(Body.class, m, i)) {
+ return new RemoteMethodArg(i, BODY, HttpPartSchema.create(Body.class, m, i));
+ }
+ return null;
}
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodBeanArg.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodBeanArg.java
new file mode 100644
index 0000000..c682a29
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodBeanArg.java
@@ -0,0 +1,112 @@
+// ***************************************************************************************************************************
+// * 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.remoteable;
+
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.ReflectionUtils.*;
+import static org.apache.juneau.httppart.HttpPartType.*;
+
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.httppart.*;
+
+/**
+ * Represents the metadata about an {@link RequestBean}-annotated argument of a method on a remote proxy interface.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul class='doctree'>
+ * <li class='link'><a class='doclink' href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies'>Overview > juneau-rest-client > Interface Proxies Against 3rd-party REST Interfaces</a>
+ * </ul>
+ */
+public final class RemoteMethodBeanArg {
+
+ private final int index;
+ private final HttpPartSerializer serializer;
+ private final Map<String,RemoteMethodArg> properties;
+
+ private RemoteMethodBeanArg(int index, Class<? extends HttpPartSerializer> serializer, Map<String,RemoteMethodArg> properties) {
+ this.index = index;
+ this.serializer = newInstance(HttpPartSerializer.class, serializer);
+ this.properties = properties;
+ }
+
+ /**
+ * Returns the index of the parameter in the method that is a request bean.
+ *
+ * @return The index of the parameter in the method that is a request bean.
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Returns the serializer to use for serializing parts on the request bean.
+ *
+ * @return The serializer to use for serializing parts on the request bean, or <jk>null</jk> if not defined.
+ */
+ public HttpPartSerializer getSerializer() {
+ return serializer;
+ }
+
+ /**
+ * Returns metadata on the specified property of the request bean.
+ *
+ * @param name The bean property name.
+ * @return Metadata about the bean property, or <jk>null</jk> if not found.
+ */
+ public RemoteMethodArg getProperty(String name) {
+ return properties.get(name);
+ }
+
+ static RemoteMethodBeanArg create(Method m, int i) {
+ Map<String,RemoteMethodArg> map = new LinkedHashMap<>();
+ if (hasAnnotation(RequestBean.class, m, i)) {
+ RequestBean rb = getAnnotation(RequestBean.class, m, i);
+ BeanMeta<?> bm = BeanContext.DEFAULT.getBeanMeta(m.getParameterTypes()[i]);
+ for (BeanPropertyMeta bp : bm.getPropertyMetas()) {
+ String n = bp.getName();
+ Annotation a = bp.getAnnotation(Path.class);
+ if (a != null) {
+ HttpPartSchema s = HttpPartSchema.create().apply(a).build();
+ map.put(n, new RemoteMethodArg(PATH, s, n));
+ }
+ a = bp.getAnnotation(Header.class);
+ if (a != null) {
+ HttpPartSchema s = HttpPartSchema.create().apply(a).build();
+ map.put(n, new RemoteMethodArg(HEADER, s, n));
+ }
+ a = bp.getAnnotation(Query.class);
+ if (a != null) {
+ HttpPartSchema s = HttpPartSchema.create().apply(a).build();
+ map.put(n, new RemoteMethodArg(QUERY, s, n));
+ }
+ a = bp.getAnnotation(FormData.class);
+ if (a != null) {
+ HttpPartSchema s = HttpPartSchema.create().apply(a).build();
+ map.put(n, new RemoteMethodArg(FORMDATA, s, n));
+ }
+ a = bp.getAnnotation(Body.class);
+ if (a != null) {
+ HttpPartSchema s = HttpPartSchema.create().apply(a).build();
+ map.put(n, new RemoteMethodArg(BODY, s, n));
+ }
+ }
+ return new RemoteMethodBeanArg(i, rb.serializer(), map);
+ }
+ return null;
+ }
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodReturn.java
similarity index 53%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodReturn.java
index f188334..8604190 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodReturn.java
@@ -13,47 +13,80 @@
package org.apache.juneau.remoteable;
import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.ReflectionUtils.*;
+import static org.apache.juneau.remoteable.ReturnValue.*;
+import java.lang.reflect.*;
+
+import org.apache.juneau.http.annotation.*;
import org.apache.juneau.httppart.*;
-import org.apache.juneau.urlencoding.*;
/**
- * Represents the metadata about an annotated argument of a method on a remote proxy interface.
+ * Represents the metadata about the returned object of a method on a remote proxy interface.
*
* <h5 class='section'>See Also:</h5>
* <ul class='doctree'>
* <li class='link'><a class='doclink' href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies'>Overview > juneau-rest-client > Interface Proxies Against 3rd-party REST Interfaces</a>
* </ul>
*/
-public class RemoteMethodArg {
+public final class RemoteMethodReturn {
- /** The argument name. Can be blank. */
- public final String name;
+ private final HttpPartParser parser;
+ private final HttpPartSchema schema;
+ private final Type returnType;
+ private final ReturnValue returnValue;
- /** The zero-based index of the argument on the Java method. */
- public final int index;
+ RemoteMethodReturn(Method m, ReturnValue returnValue) {
+ this.returnType = m.getGenericReturnType();
+ if (hasAnnotation(Body.class, m)) {
+ this.schema = HttpPartSchema.create(Body.class, m);
+ this.parser = newInstance(HttpPartParser.class, schema.getParser());
+ } else {
+ this.schema = null;
+ this.parser = null;
+ }
+ if (returnValue == null) {
+ if (m.getReturnType() == void.class)
+ returnValue = NONE;
+ else
+ returnValue = BODY;
+ }
+ this.returnValue = returnValue;
+ }
- /** The value is skipped if it's null/empty. */
- public final boolean skipIfNE;
+ /**
+ * Returns the parser to use for parsing this part.
+ *
+ * @return The parser to use for parsing this part, or <jk>null</jk> if not specified.
+ */
+ public HttpPartParser getParser() {
+ return parser;
+ }
- /** The serializer used for converting objects to strings. */
- public final HttpPartSerializer serializer;
+ /**
+ * Returns schema information about the HTTP part.
+ *
+ * @return Schema information about the HTTP part, or <jk>null</jk> if not found.
+ */
+ public HttpPartSchema getSchema() {
+ return schema;
+ }
+
+ /**
+ * Returns the class type of the method return.
+ *
+ * @return The class type of the method return.
+ */
+ public Type getReturnType() {
+ return returnType;
+ }
/**
- * Constructor.
+ * Specifies whether the return value is the body of the request or the HTTP status.
*
- * @param name The argument name pulled from name().
- * @param name2 The argument name pulled from value().
- * @param index The zero-based index of the argument on the Java method.
- * @param skipIfNE The value is skipped if it's null/empty.
- * @param serializer
- * The class to use for serializing headers, query parameters, form-data parameters, and path variables.
- * If {@link UrlEncodingSerializer}, then the url-encoding serializer defined on the client will be used.
+ * @return The type of value returned.
*/
- protected RemoteMethodArg(String name, String name2, int index, boolean skipIfNE, Class<? extends HttpPartSerializer> serializer) {
- this.name = name.isEmpty() ? name2 : name;
- this.index = index;
- this.skipIfNE = skipIfNE;
- this.serializer = newInstance(HttpPartSerializer.class, serializer);
+ public ReturnValue getReturnValue() {
+ return returnValue;
}
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
index 28516f9..ca3a71a 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
@@ -14,12 +14,13 @@ package org.apache.juneau.remoteable;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.httppart.HttpPartType.*;
-import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.httppart.*;
/**
* Contains the meta-data about a Java method on a remoteable interface.
@@ -36,10 +37,10 @@ public class RemoteableMethodMeta {
private final String httpMethod;
private final String url;
- private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, formDataArgs, requestBeanArgs;
- private final Integer[] otherArgs;
- private final Integer bodyArg;
- private final ReturnValue returnValue;
+ private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, formDataArgs, otherArgs;
+ private final RemoteMethodBeanArg[] requestBeanArgs;
+ private final RemoteMethodArg bodyArg;
+ private final RemoteMethodReturn methodReturn;
/**
* Constructor.
@@ -55,10 +56,10 @@ public class RemoteableMethodMeta {
this.queryArgs = b.queryArgs.toArray(new RemoteMethodArg[b.queryArgs.size()]);
this.formDataArgs = b.formDataArgs.toArray(new RemoteMethodArg[b.formDataArgs.size()]);
this.headerArgs = b.headerArgs.toArray(new RemoteMethodArg[b.headerArgs.size()]);
- this.requestBeanArgs = b.requestBeanArgs.toArray(new RemoteMethodArg[b.requestBeanArgs.size()]);
- this.otherArgs = b.otherArgs.toArray(new Integer[b.otherArgs.size()]);
+ this.requestBeanArgs = b.requestBeanArgs.toArray(new RemoteMethodBeanArg[b.requestBeanArgs.size()]);
+ this.otherArgs = b.otherArgs.toArray(new RemoteMethodArg[b.otherArgs.size()]);
this.bodyArg = b.bodyArg;
- this.returnValue = b.returnValue;
+ this.methodReturn = b.methodReturn;
}
private static final class Builder {
@@ -68,11 +69,11 @@ public class RemoteableMethodMeta {
queryArgs = new LinkedList<>(),
headerArgs = new LinkedList<>(),
formDataArgs = new LinkedList<>(),
- requestBeanArgs = new LinkedList<>();
- List<Integer>
otherArgs = new LinkedList<>();
- Integer bodyArg;
- ReturnValue returnValue;
+ List<RemoteMethodBeanArg>
+ requestBeanArgs = new LinkedList<>();
+ RemoteMethodArg bodyArg;
+ RemoteMethodReturn methodReturn;
Builder(String restUrl, Method m) {
Remoteable r = m.getDeclaringClass().getAnnotation(Remoteable.class);
@@ -90,50 +91,43 @@ public class RemoteableMethodMeta {
throw new RemoteableMetadataException(m,
"Invalid value specified for @Remoteable.methodPaths() annotation. Valid values are [NAME,SIGNATURE].");
- returnValue = rm == null ? ReturnValue.BODY : rm.returns();
+ ReturnValue rv = m.getReturnType() == void.class ? ReturnValue.NONE : rm == null ? ReturnValue.BODY : rm.returns();
+
+ methodReturn = new RemoteMethodReturn(m, rv);
url =
trimSlashes(restUrl)
+ '/'
+ (path != null ? trimSlashes(path) : urlEncode("NAME".equals(methodPaths) ? m.getName() : getMethodSignature(m)));
- int index = 0;
- for (Annotation[] aa : m.getParameterAnnotations()) {
+ for (int i = 0; i < m.getParameterTypes().length; i++) {
+ RemoteMethodArg rma = RemoteMethodArg.create(m, i);
boolean annotated = false;
- for (Annotation a : aa) {
- Class<?> ca = a.annotationType();
- if (ca == Path.class) {
- Path p = (Path)a;
- annotated = pathArgs.add(new RemoteMethodArg(p.name(), p.value(), index, false, p.serializer()));
- } else if (ca == Query.class) {
- Query q = (Query)a;
- annotated = queryArgs.add(new RemoteMethodArg(q.name(), q.value(), index, q.skipIfEmpty(), q.serializer()));
- } else if (ca == FormData.class) {
- FormData f = (FormData)a;
- annotated = formDataArgs.add(new RemoteMethodArg(f.name(), f.value(), index, f.skipIfEmpty(), f.serializer()));
- } else if (ca == Header.class) {
- Header h = (Header)a;
- annotated = headerArgs.add(new RemoteMethodArg(h.name(), h.value(), index, h.skipIfEmpty(), h.serializer()));
- } else if (ca == RequestBean.class) {
- RequestBean rb = (RequestBean)a;
- annotated = requestBeanArgs.add(new RemoteMethodArg("", "", index, false, rb.serializer()));
- } else if (ca == Body.class) {
- annotated = true;
- if (bodyArg == null)
- bodyArg = index;
- else
- throw new RemoteableMetadataException(m,
- "Multiple @Body parameters found. Only one can be specified per Java method.");
- }
+ if (rma != null) {
+ annotated = true;
+ HttpPartType pt = rma.getPartType();
+ if (pt == HEADER)
+ headerArgs.add(rma);
+ else if (pt == QUERY)
+ queryArgs.add(rma);
+ else if (pt == FORMDATA)
+ formDataArgs.add(rma);
+ else if (pt == PATH)
+ pathArgs.add(rma);
+ else if (pt == BODY)
+ bodyArg = rma;
+ else
+ annotated = false;
+ }
+ RemoteMethodBeanArg rmba = RemoteMethodBeanArg.create(m, i);
+ if (rmba != null) {
+ annotated = true;
+ requestBeanArgs.add(rmba);
+ }
+ if (! annotated) {
+ otherArgs.add(new RemoteMethodArg(i, BODY, null));
}
- if (! annotated)
- otherArgs.add(index);
- index++;
}
-
- if (bodyArg != null && otherArgs.size() > 0)
- throw new RemoteableMetadataException(m,
- "@Body and non-annotated parameters found together. Non-annotated parameters cannot be used when @Body is used.");
}
}
@@ -196,7 +190,7 @@ public class RemoteableMethodMeta {
*
* @return A list of zero-indexed argument indices.
*/
- public RemoteMethodArg[] getRequestBeanArgs() {
+ public RemoteMethodBeanArg[] getRequestBeanArgs() {
return requestBeanArgs;
}
@@ -205,7 +199,7 @@ public class RemoteableMethodMeta {
*
* @return A list of zero-indexed argument indices.
*/
- public Integer[] getOtherArgs() {
+ public RemoteMethodArg[] getOtherArgs() {
return otherArgs;
}
@@ -214,7 +208,7 @@ public class RemoteableMethodMeta {
*
* @return A index of the argument with the {@link Body @Body} annotation, or <jk>null</jk> if no argument exists.
*/
- public Integer getBodyArg() {
+ public RemoteMethodArg getBodyArg() {
return bodyArg;
}
@@ -223,7 +217,7 @@ public class RemoteableMethodMeta {
*
* @return Whether the method returns the HTTP response body or status code.
*/
- public ReturnValue getReturns() {
- return returnValue;
+ public RemoteMethodReturn getReturns() {
+ return methodReturn;
}
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/ReturnValue.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/ReturnValue.java
index c438fb0..1c92b6b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/ReturnValue.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/ReturnValue.java
@@ -21,5 +21,8 @@ public enum ReturnValue {
BODY,
/** HTTP status code */
- HTTP_STATUS;
+ HTTP_STATUS,
+
+ /** Ignore (used for void methods) */
+ NONE;
}
diff --git a/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/VarResolverSession.java b/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/VarResolverSession.java
index fb8c6f0..ee13c93 100644
--- a/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/VarResolverSession.java
+++ b/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/VarResolverSession.java
@@ -109,7 +109,7 @@ public class VarResolverSession {
} catch (VarResolverException e) {
throw e;
} catch (Exception e) {
- throw new VarResolverException(e, "Problem occurred resolving variable ''{0}''", var);
+ throw new VarResolverException(e, "Problem occurred resolving variable ''{0}'' in string ''{1}''", var, s);
}
}
return s;
@@ -353,7 +353,7 @@ public class VarResolverSession {
} catch (VarResolverException e) {
throw e;
} catch (Exception e) {
- throw new VarResolverException(e, "Problem occurred resolving variable ''{0}''", varType);
+ throw new VarResolverException(e, "Problem occurred resolving variable ''{0}'' in string ''{1}''", varType, s);
}
x = i+1;
}
diff --git a/juneau-doc/src/main/javadoc/overview.html b/juneau-doc/src/main/javadoc/overview.html
index b89af1d..ee13f83 100644
--- a/juneau-doc/src/main/javadoc/overview.html
+++ b/juneau-doc/src/main/javadoc/overview.html
@@ -369,6 +369,12 @@
<li><p class='toc2'><a class='doclink' href='#juneau-rest-client'><i>juneau-rest-client</i></a></p>
<ol>
<li><p><a class='doclink' href='#juneau-rest-client.3rdPartyProxies'>Interface Proxies Against 3rd-party REST Interfaces</a></p>
+ <ul>
+ <li><p><a class='doclink' href='#juneau-rest-client.3rdPartyProxies.Body'>@Body</a></p>
+ <li><p><a class='doclink' href='#juneau-rest-client.3rdPartyProxies.FormData'>@FormData</a></p>
+ <li><p><a class='doclink' href='#juneau-rest-client.3rdPartyProxies.Query'>@Query</a></p>
+ <li><p><a class='doclink' href='#juneau-rest-client.3rdPartyProxies.Header'>@Header</a></p>
+ </ul>
<li><p><a class='doclink' href='#juneau-rest-client.SSL'>SSL Support</a></p>
<li><p><a class='doclink' href='#juneau-rest-client.Authentication'>Authentication</a></p>
<ol>
@@ -12791,9 +12797,9 @@
</p>
<ul class='spaced-list'>
<li>
- Parameters on a {@link org.apache.juneau.rest.annotation.RestMethod @RestMethod}.
+ Parameters on a <ja>@RestMethod</ja>-annotated method.
<li>
- POJO classes.
+ POJO classes used as parameters on a <ja>@RestMethod</ja>-annotated method.
</ul>
<h5 class='figure'>Examples:</h5>
<p class='bcode w800'>
@@ -12927,6 +12933,36 @@
}
}
</p>
+ <p>
+ This annotation is also used for supplying swagger information about the body of the request.
+ <br>This information is used to populate the auto-generated Swagger documentation and UI.
+ </p>
+ <h5 class='section'>Examples:</h5>
+ <p class='bcode w800'>
+ <jc>// Normal</jc>
+ <ja>@Body</ja>(
+ description=<js>"Pet object to add to the store"</js>,
+ required=<js>"true"</js>,
+ example=<js>"{name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']}"</js>
+ )
+ </p>
+ <p class='bcode w800'>
+ <jc>// Free-form</jc>
+ <ja>@Body</ja>({
+ <js>"description: 'Pet object to add to the store',"</js>,
+ <js>"required: true,"</js>,
+ <js>"example: {name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']},"</js>
+ <js>"x-extra: 'extra field'"</js>
+ })
+ </p>
+ <p class='bcode w800'>
+ <jc>// Localized</jc>
+ <ja>@Body</ja>(
+ description=<js>"$L{My.Localized.Description}"</js>,
+ required=<js>"true"</js>,
+ example=<js>"{name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']}"</js>
+ )
+ </p>
<h5 class='section'>Other Notes:</h5>
<ul class='spaced-list'>
@@ -17199,6 +17235,41 @@
<p>
The Java method arguments can be annotated with any of the following:
</p>
+ <ul class='doctree'>
+ <li class='ja'>{@link org.apache.juneau.http.annotation.Query} - A URL query parameter.
+ <li class='ja'>{@link org.apache.juneau.http.annotation.FormData} - A form-data parameter.
+ <li class='ja'>{@link org.apache.juneau.http.annotation.Header} - A request header.
+ <li class='ja'>{@link org.apache.juneau.http.annotation.Body} - The HTTP request body.
+ </ul>
+ <p>
+ The return type of the Java method can be any of the following:
+ </p>
+ <ul class='spaced-list'>
+ <li>
+ <jk>void</jk>
+ - Don't parse any response.
+ <br>Note that the method will still throw a runtime exception if an error HTTP status is returned.
+ <li>
+ Any <a class='doclink' href='#juneau-marshall.PojoCategories'>parsable</a> POJO
+ - The body of the response will be converted to the POJO using the parser defined on the
+ <code>RestClient</code> based on the <code>Content-Type</code> of the response.
+ <li>
+ <code>HttpResponse</code>
+ - Returns the raw <code>HttpResponse</code> returned by the inner <code>HttpClient</code>.
+ <li>
+ {@link java.io.Reader}
+ - Returns access to the raw reader of the response.
+ <br>Note that if you don't want your response parsed as a POJO, you'll want to get the response reader
+ directly.
+ <li>
+ {@link java.io.InputStream}
+ - Returns access to the raw input stream of the response.
+ </ul>
+
+ <!-- === 9.1.1 - @Body ========================================================================== -->
+
+ <h4 class='topic' onclick='toggle(this)'><a href='#juneau-rest-client.3rdPartyProxies.Body' id='juneau-rest-client.3rdPartyProxies.Body'>9.1.1 - @Body</a></h4>
+ <div class='topic'>
<ul class='spaced-list'>
<li class='ja'>
{@link org.apache.juneau.http.annotation.Query} - A URL query parameter.
@@ -17254,31 +17325,190 @@
<li class='normal'>{@link org.apache.juneau.rest.client.NameValuePairs}
- Converted to a URL-encoded FORM post.
</ul>
- </ul>
- <p>
- The return type of the Java method can be any of the following:
- </p>
+ </div>
+
+ <!-- === 9.1.2 - @FormData ====================================================================== -->
+
+ <h4 class='topic' onclick='toggle(this)'><a href='#juneau-rest-client.3rdPartyProxies.FormData' id='juneau-rest-client.3rdPartyProxies.FormData'>9.1.2 - @FormData</a></h4>
+ <div class='topic'>
<ul class='spaced-list'>
- <li>
- <jk>void</jk>
- - Don't parse any response.
- <br>Note that the method will still throw a runtime exception if an error HTTP status is returned.
- <li>
- Any <a class='doclink' href='#juneau-marshall.PojoCategories'>parsable</a> POJO
- - The body of the response will be converted to the POJO using the parser defined on the
- <code>RestClient</code>.
- <li>
- <code>HttpResponse</code>
- - Returns the raw <code>HttpResponse</code> returned by the inner <code>HttpClient</code>.
- <li>
- {@link java.io.Reader}
- - Returns access to the raw reader of the response.
- <br>Note that if you don't want your response parsed as a POJO, you'll want to get the response reader
- directly.
- <li>
- {@link java.io.InputStream}
- - Returns access to the raw input stream of the response.
- </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.Query} - A URL query parameter.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'><code>Map<String,Object></code>
+ - Individual name-value pairs.
+ <br>Values are converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'><code>String</code>
+ - Treated as a query string.
+ </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.FormData}
+ - A form-data parameter.
+ <br>Note that this is only available if the HTTP method is <code>POST</code>.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'>{@link org.apache.juneau.rest.client.NameValuePairs}
+ - Individual name-value pairs.
+ <li class='normal'><code>Map<String,Object></code>
+ - Individual name-value pairs.
+ <br>Values are converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.Header}
+ - A request header.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'><code>Map<String,Object></code>
+ - Individual name-value pairs.
+ <br>Values are converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.Body}
+ - The HTTP request body.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text/bytes using the {@link org.apache.juneau.serializer.Serializer} registered
+ with the <code>RestClient</code>.
+ <li class='normal'>{@link java.io.Reader}
+ - Raw contents of reader will be serialized to remote resource.
+ <li class='normal'>{@link java.io.InputStream}
+ - Raw contents of input stream will be serialized to remote resource.
+ <li class='normal'>{@link org.apache.http.HttpEntity}
+ - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
+ <li class='normal'>{@link org.apache.juneau.rest.client.NameValuePairs}
+ - Converted to a URL-encoded FORM post.
+ </ul>
+ </div>
+
+ <!-- === 9.1.3 - @Query ========================================================================= -->
+
+ <h4 class='topic' onclick='toggle(this)'><a href='#juneau-rest-client.3rdPartyProxies.Query' id='juneau-rest-client.3rdPartyProxies.Query'>9.1.3 - @Query</a></h4>
+ <div class='topic'>
+ <ul class='spaced-list'>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.Query} - A URL query parameter.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'><code>Map<String,Object></code>
+ - Individual name-value pairs.
+ <br>Values are converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'><code>String</code>
+ - Treated as a query string.
+ </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.FormData}
+ - A form-data parameter.
+ <br>Note that this is only available if the HTTP method is <code>POST</code>.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'>{@link org.apache.juneau.rest.client.NameValuePairs}
+ - Individual name-value pairs.
+ <li class='normal'><code>Map<String,Object></code>
+ - Individual name-value pairs.
+ <br>Values are converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.Header}
+ - A request header.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'><code>Map<String,Object></code>
+ - Individual name-value pairs.
+ <br>Values are converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.Body}
+ - The HTTP request body.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text/bytes using the {@link org.apache.juneau.serializer.Serializer} registered
+ with the <code>RestClient</code>.
+ <li class='normal'>{@link java.io.Reader}
+ - Raw contents of reader will be serialized to remote resource.
+ <li class='normal'>{@link java.io.InputStream}
+ - Raw contents of input stream will be serialized to remote resource.
+ <li class='normal'>{@link org.apache.http.HttpEntity}
+ - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
+ <li class='normal'>{@link org.apache.juneau.rest.client.NameValuePairs}
+ - Converted to a URL-encoded FORM post.
+ </ul>
+ </div>
+
+ <!-- === 9.1.4 - @Header ======================================================================== -->
+
+ <h4 class='topic' onclick='toggle(this)'><a href='#juneau-rest-client.3rdPartyProxies.Header' id='juneau-rest-client.3rdPartyProxies.Header'>9.1.4 - @Header</a></h4>
+ <div class='topic'>
+ <ul class='spaced-list'>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.Query} - A URL query parameter.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'><code>Map<String,Object></code>
+ - Individual name-value pairs.
+ <br>Values are converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'><code>String</code>
+ - Treated as a query string.
+ </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.FormData}
+ - A form-data parameter.
+ <br>Note that this is only available if the HTTP method is <code>POST</code>.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'>{@link org.apache.juneau.rest.client.NameValuePairs}
+ - Individual name-value pairs.
+ <li class='normal'><code>Map<String,Object></code>
+ - Individual name-value pairs.
+ <br>Values are converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.Header}
+ - A request header.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ <li class='normal'><code>Map<String,Object></code>
+ - Individual name-value pairs.
+ <br>Values are converted to text using {@link org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+ </ul>
+ <li class='ja'>
+ {@link org.apache.juneau.http.annotation.Body}
+ - The HTTP request body.
+ <br>The argument can be any of the following types:
+ <ul>
+ <li class='normal'>Any serializable POJO
+ - Converted to text/bytes using the {@link org.apache.juneau.serializer.Serializer} registered
+ with the <code>RestClient</code>.
+ <li class='normal'>{@link java.io.Reader}
+ - Raw contents of reader will be serialized to remote resource.
+ <li class='normal'>{@link java.io.InputStream}
+ - Raw contents of input stream will be serialized to remote resource.
+ <li class='normal'>{@link org.apache.http.HttpEntity}
+ - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
+ <li class='normal'>{@link org.apache.juneau.rest.client.NameValuePairs}
+ - Converted to a URL-encoded FORM post.
+ </ul>
+ </div>
</div>
<!-- === 9.2 - SSL Support ========================================================================== -->
@@ -22835,6 +23065,8 @@
<h5 class='topic w800'>juneau-rest-client</h5>
<ul class='spaced-list'>
<li>
+ Remoteable interfaces support OpenAPI annotations.
+ <li>
Made improvements to the builder API for defining SSL support.
<br>New methods added:
<ul class='doctree'>
@@ -22886,6 +23118,11 @@
<li class='jm'>{@link org.apache.juneau.rest.widget.Widget#loadScriptWithVars(RestRequest,String) loadScriptWithVars(RestRequest,String)}
<li class='jm'>{@link org.apache.juneau.rest.widget.Widget#loadStyleWithVars(RestRequest,String) loadStyleWithVars(RestRequest,String)}
</ul>
+ <li>
+ Removed the deprecated <code>RestCall.execute()</code> method.
+ <br>Use {@link org.apache.juneau.rest.client.RestCall#run()}.
+ <li>
+ <code>RestCall.input(Object)</code> method renamed to {@link org.apache.juneau.rest.client.RestCall#body(Object)} to match OpenAPI terminology.
</ul>
<h5 class='topic w800'>juneau-rest-microservice</h5>
diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/RequestBeanProxyTest.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/RequestBeanProxyTest.java
index ff39f9f..7d5f064 100644
--- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/RequestBeanProxyTest.java
+++ b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/RequestBeanProxyTest.java
@@ -69,7 +69,7 @@ public class RequestBeanProxyTest {
@Query("b") String getX1();
@Query(name="c") String getX2();
@Query @BeanProperty("d") String getX3();
- @Query("e") String getX4();
+ @Query(name="e",allowEmptyValue=true) String getX4();
@Query("f") String getX5();
@Query("g") String getX6();
@Query("h") String getX7();
@@ -125,7 +125,7 @@ public class RequestBeanProxyTest {
public Map<String,Object> getB() {
return new AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", "null");
}
- @Query(name="*")
+ @Query(name="*",allowEmptyValue=true)
public Map<String,Object> getC() {
return new AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
}
@@ -141,17 +141,17 @@ public class RequestBeanProxyTest {
@Test
public void a02a_query_maps_plainText() throws Exception {
String r = a02a.normal(new A02_Bean());
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
+ assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
}
@Test
public void a02b_query_maps_uon() throws Exception {
String r = a02b.normal(new A02_Bean());
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}", r);
+ assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}", r);
}
@Test
public void a02c_query_maps_x() throws Exception {
String r = a02b.serialized(new A02_Bean());
- assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
+ assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
}
//=================================================================================================================
@@ -169,7 +169,7 @@ public class RequestBeanProxyTest {
}
public static class A03_Bean {
- @Query
+ @Query(allowEmptyValue=true)
public NameValuePairs getA() {
return new NameValuePairs().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
}
@@ -177,7 +177,7 @@ public class RequestBeanProxyTest {
public NameValuePairs getB() {
return new NameValuePairs().append("b1","true").append("b2", "123").append("b3", "null");
}
- @Query(name="*")
+ @Query(name="*",allowEmptyValue=true)
public NameValuePairs getC() {
return new NameValuePairs().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
}
@@ -198,12 +198,12 @@ public class RequestBeanProxyTest {
@Test
public void a03b_query_nameValuePairs_on() throws Exception {
String r = a03b.normal(new A03_Bean());
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
+ assertEquals("{a1:'v1',a2:'\\'123\\'',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'\\'123\\'',c4:''}", r);
}
@Test
public void a03c_query_nameValuePairs_x() throws Exception {
String r = a03b.serialized(new A03_Bean());
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
+ assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
}
//=================================================================================================================
@@ -283,7 +283,7 @@ public class RequestBeanProxyTest {
public List<Object> getX2() {
return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
}
- @Query("d")
+ @Query(name="d",allowEmptyValue=true)
public List<Object> getX3() {
return new AList<>();
}
@@ -299,7 +299,7 @@ public class RequestBeanProxyTest {
public Object[] getX6() {
return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
}
- @Query("h")
+ @Query(name="h",allowEmptyValue=true)
public Object[] getX7() {
return new Object[]{};
}
@@ -325,7 +325,7 @@ public class RequestBeanProxyTest {
@Test
public void a06c_query_collections_x() throws Exception {
String r = a06b.serialized(new A06_Bean());
- assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'fooXXtrueX123XnullXtrueX123Xnull',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'fooXXtrueX123XnullXtrueX123Xnull',h:''}", r);
+ assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'foo||true|123|null|true|123|null',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'foo||true|123|null|true|123|null',h:''}", r);
}
//=================================================================================================================
@@ -373,7 +373,7 @@ public class RequestBeanProxyTest {
public String getX3() {
return "d1";
}
- @FormData("e")
+ @FormData(name="e",allowEmptyValue=true)
public String getX4() {
return "";
}
@@ -433,7 +433,7 @@ public class RequestBeanProxyTest {
public Map<String,Object> getB() {
return new AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", "null");
}
- @FormData(name="*")
+ @FormData(name="*",allowEmptyValue=true)
public Map<String,Object> getC() {
return new AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
}
@@ -449,17 +449,17 @@ public class RequestBeanProxyTest {
@Test
public void c02a_formData_maps_plainText() throws Exception {
String r = c02a.normal(new C02_Bean());
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
+ assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
}
@Test
public void c02b_formData_maps_uon() throws Exception {
String r = c02b.normal(new C02_Bean());
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}", r);
+ assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}", r);
}
@Test
public void c02c_formData_maps_x() throws Exception {
String r = c02b.serialized(new C02_Bean());
- assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
+ assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
}
//=================================================================================================================
@@ -591,7 +591,7 @@ public class RequestBeanProxyTest {
public List<Object> getX2() {
return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
}
- @FormData("d")
+ @FormData(name="d",allowEmptyValue=true)
public List<Object> getX3() {
return new AList<>();
}
@@ -607,7 +607,7 @@ public class RequestBeanProxyTest {
public Object[] getX6() {
return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
}
- @FormData("h")
+ @FormData(name="h",allowEmptyValue=true)
public Object[] getX7() {
return new Object[]{};
}
@@ -633,7 +633,7 @@ public class RequestBeanProxyTest {
@Test
public void c06c_formData_collections_x() throws Exception {
String r = c06b.serialized(new C06_Bean());
- assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'fooXXtrueX123XnullXtrueX123Xnull',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'fooXXtrueX123XnullXtrueX123Xnull',h:''}", r);
+ assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'foo||true|123|null|true|123|null',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'foo||true|123|null|true|123|null',h:''}", r);
}
@@ -682,7 +682,7 @@ public class RequestBeanProxyTest {
public String getX3() {
return "d1";
}
- @Header("e")
+ @Header(name="e",allowEmptyValue=true)
public String getX4() {
return "";
}
@@ -742,7 +742,7 @@ public class RequestBeanProxyTest {
public Map<String,Object> getB() {
return new AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", "null");
}
- @Header(name="*")
+ @Header(name="*",allowEmptyValue=true)
public Map<String,Object> getC() {
return new AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
}
@@ -758,17 +758,17 @@ public class RequestBeanProxyTest {
@Test
public void e02a_header_maps_plainText() throws Exception {
String r = e02a.normal(new E02_Bean());
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
+ assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
}
@Test
public void e02b_header_maps_uon() throws Exception {
String r = e02b.normal(new E02_Bean());
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}", r);
+ assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}", r);
}
@Test
public void e02c_header_maps_x() throws Exception {
String r = e02b.serialized(new E02_Bean());
- assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
+ assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
}
//=================================================================================================================
@@ -850,7 +850,7 @@ public class RequestBeanProxyTest {
public List<Object> getX2() {
return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
}
- @Header("d")
+ @Header(name="d",allowEmptyValue=true)
public List<Object> getX3() {
return new AList<>();
}
@@ -866,7 +866,7 @@ public class RequestBeanProxyTest {
public Object[] getX6() {
return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
}
- @Header("h")
+ @Header(name="h",allowEmptyValue=true)
public Object[] getX7() {
return new Object[]{};
}
@@ -892,7 +892,7 @@ public class RequestBeanProxyTest {
@Test
public void e04c_header_collections_x() throws Exception {
String r = e04b.serialized(new E04_Bean());
- assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'fooXXtrueX123XnullXtrueX123Xnull',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'fooXXtrueX123XnullXtrueX123Xnull',h:''}", r);
+ assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'foo||true|123|null|true|123|null',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'foo||true|123|null|true|123|null',h:''}", r);
}
//=================================================================================================================
@@ -940,7 +940,7 @@ public class RequestBeanProxyTest {
public String getX3() {
return "d1";
}
- @Path("e")
+ @Path(name="e",allowEmptyValue=true)
public String getX4() {
return "";
}
@@ -992,7 +992,7 @@ public class RequestBeanProxyTest {
}
public static class G02_Bean {
- @Path
+ @Path(name="*",allowEmptyValue=true)
public Map<String,Object> getA() {
return new AMap<String,Object>().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
}
@@ -1000,7 +1000,7 @@ public class RequestBeanProxyTest {
public Map<String,Object> getB() {
return new AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", "null");
}
- @Path(name="*")
+ @Path(name="*",allowEmptyValue=true)
public Map<String,Object> getC() {
return new AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
}
@@ -1044,7 +1044,7 @@ public class RequestBeanProxyTest {
}
public static class G03_Bean {
- @Path
+ @Path(name="*",allowEmptyValue=true)
public NameValuePairs getA() {
return new NameValuePairs().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
}
@@ -1052,7 +1052,7 @@ public class RequestBeanProxyTest {
public NameValuePairs getB() {
return new NameValuePairs().append("b1","true").append("b2", "123").append("b3", "null");
}
- @Path(name="*")
+ @Path(name="*",allowEmptyValue=true)
public NameValuePairs getC() {
return new NameValuePairs().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
}
@@ -1108,7 +1108,7 @@ public class RequestBeanProxyTest {
public List<Object> getX2() {
return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
}
- @Path("d")
+ @Path(name="d",allowEmptyValue=true)
public List<Object> getX3() {
return new AList<>();
}
@@ -1124,7 +1124,7 @@ public class RequestBeanProxyTest {
public Object[] getX6() {
return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
}
- @Path("h")
+ @Path(name="h",allowEmptyValue=true)
public Object[] getX7() {
return new Object[]{};
}
@@ -1150,7 +1150,7 @@ public class RequestBeanProxyTest {
@Test
public void g04c_path_collections_x() throws Exception {
String r = g04b.serialized(new G04_Bean());
- assertEquals("echoPath/fooXXtrueX123XnullXtrueX123Xnull/fooXXtrueX123XnullXtrueX123Xnull/fooXXtrueX123XnullXtrueX123Xnull//NULL/fooXXtrueX123XnullXtrueX123Xnull/fooXXtrueX123XnullXtrueX123Xnull//NULL", r);
+ assertEquals("echoPath/fooXXtrueX123XnullXtrueX123Xnull/fooXXtrueX123XnullXtrueX123Xnull/foo||true|123|null|true|123|null//NULL/fooXXtrueX123XnullXtrueX123Xnull/foo||true|123|null|true|123|null//NULL", r);
}
//=================================================================================================================
diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/ThirdPartyProxyTest.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/ThirdPartyProxyTest.java
index dc9b589..2fe57e3 100644
--- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/ThirdPartyProxyTest.java
+++ b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/ThirdPartyProxyTest.java
@@ -2151,7 +2151,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
);
public static interface ReqBeanPath6 {
- @Path
+ @Path("*")
Map<String,Object> getX();
}
@@ -2161,7 +2161,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
);
public static interface ReqBeanPath7 {
- @Path
+ @Path("*")
ABean getX();
}
@@ -2260,7 +2260,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
);
public static interface ReqBeanQuery6 {
- @Query
+ @Query("*")
Map<String,Object> getX();
}
@@ -2270,7 +2270,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
);
public static interface ReqBeanQuery7 {
- @Query
+ @Query("*")
ABean getX();
}
@@ -2369,7 +2369,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
);
public static interface ReqBeanFormData6 {
- @FormData
+ @FormData("*")
Map<String,Object> getX();
}
@@ -2379,7 +2379,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
);
public static interface ReqBeanFormData7 {
- @FormData
+ @FormData("*")
ABean getX();
}
@@ -2478,7 +2478,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
);
public static interface ReqBeanHeader6 {
- @Header
+ @Header("*")
Map<String,Object> getX();
}
@@ -2488,7 +2488,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
);
public static interface ReqBeanHeader7 {
- @Header
+ @Header("*")
ABean getX();
}
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java
index 66c637d..ceb375c 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java
@@ -28,7 +28,7 @@ import org.apache.juneau.urlencoding.*;
*
* <p>
* Instances of this method can be passed directly to the {@link RestClient#doPost(Object, Object)} method or
- * {@link RestCall#input(Object)} methods to perform URL-encoded form posts.
+ * {@link RestCall#body(Object)} methods to perform URL-encoded form posts.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode'>
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
index eb574ae..7c1e145 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
@@ -93,6 +93,7 @@ public final class RestCall extends BeanSession implements Closeable {
private boolean hasInput; // input() was called, even if it's setting 'null'.
private Serializer serializer;
private Parser parser;
+ private HttpPartParser partParser;
private URIBuilder uriBuilder;
private NameValuePairs formData;
@@ -115,6 +116,7 @@ public final class RestCall extends BeanSession implements Closeable {
this.retryInterval = client.retryInterval;
this.serializer = client.serializer;
this.parser = client.parser;
+ this.partParser = client.getPartParser();
uriBuilder = new URIBuilder(uri);
}
@@ -207,16 +209,19 @@ public final class RestCall extends BeanSession implements Closeable {
public RestCall query(String name, Object value, boolean skipIfEmpty, HttpPartSerializer serializer, HttpPartSchema schema) throws RestCallException {
if (serializer == null)
serializer = client.getPartSerializer();
- if (! ("*".equals(name) || isEmpty(name))) {
+ boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairs;
+ if (! isMulti) {
if (value != null && ! (ObjectUtils.isEmpty(value) && skipIfEmpty))
try {
uriBuilder.addParameter(name, serializer.createSession(null).serialize(HttpPartType.QUERY, schema, value));
- } catch (SerializeException | SchemaValidationException e) {
- throw new RestCallException(e);
+ } catch (SchemaValidationException e) {
+ throw new RestCallException(e, "Validation error on request query parameter ''{0}''=''{1}''", name, value);
+ } catch (SerializeException e) {
+ throw new RestCallException(e, "Serialization error on request query parameter ''{0}''", name);
}
} else if (value instanceof NameValuePairs) {
for (NameValuePair p : (NameValuePairs)value)
- query(p.getName(), p.getValue(), skipIfEmpty, SimpleUonPartSerializer.DEFAULT, schema);
+ query(p.getName(), p.getValue(), skipIfEmpty, serializer, schema);
} else if (value instanceof Map) {
for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet())
query(p.getKey(), p.getValue(), skipIfEmpty, serializer, schema);
@@ -327,7 +332,8 @@ public final class RestCall extends BeanSession implements Closeable {
formData = new NameValuePairs();
if (serializer == null)
serializer = client.getPartSerializer();
- if (! ("*".equals(name) || isEmpty(name))) {
+ boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairs;
+ if (! isMulti) {
if (value != null && ! (ObjectUtils.isEmpty(value) && skipIfEmpty))
formData.add(new SerializedNameValuePair(name, value, serializer, schema));
} else if (value instanceof NameValuePairs) {
@@ -341,11 +347,11 @@ public final class RestCall extends BeanSession implements Closeable {
return formData(name, toBeanMap(value), skipIfEmpty, serializer, schema);
} else if (value instanceof Reader) {
contentType("application/x-www-form-urlencoded");
- input(value);
+ body(value);
} else if (value instanceof CharSequence) {
try {
contentType("application/x-www-form-urlencoded");
- input(new StringEntity(value.toString()));
+ body(new StringEntity(value.toString()));
} catch (UnsupportedEncodingException e) {}
} else {
throw new FormattedRuntimeException("Invalid name ''{0}'' passed to formData(name,value,skipIfEmpty) for data type ''{1}''", name, getReadableClassNameForObject(value));
@@ -440,14 +446,17 @@ public final class RestCall extends BeanSession implements Closeable {
String path = uriBuilder.getPath();
if (serializer == null)
serializer = client.getPartSerializer();
- if (! ("*".equals(name) || isEmpty(name))) {
+ boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairs;
+ if (! isMulti) {
String var = "{" + name + "}";
if (path.indexOf(var) == -1)
throw new RestCallException("Path variable {"+name+"} was not found in path.");
try {
uriBuilder.setPath(path.replace(var, serializer.createSession(null).serialize(HttpPartType.PATH, schema, value)));
- } catch (SerializeException | SchemaValidationException e) {
- throw new RestCallException(e);
+ } catch (SchemaValidationException e) {
+ throw new RestCallException(e, "Validation error on request path parameter ''{0}''=''{1}''", name, value);
+ } catch (SerializeException e) {
+ throw new RestCallException(e, "Serialization error on request path parameter ''{0}''", name);
}
} else if (value instanceof NameValuePairs) {
for (NameValuePair p : (NameValuePairs)value)
@@ -501,11 +510,9 @@ public final class RestCall extends BeanSession implements Closeable {
/**
* Sets the input for this REST call.
*
- * TODO - Describe allowed input if serializer not defined.
- *
* @param input
- * The input to be sent to the REST resource (only valid for PUT and POST) requests. <br>
- * Can be of the following types:
+ * The input to be sent to the REST resource (only valid for PUT and POST) requests.
+ * <br>Can be of the following types:
* <ul class='spaced-list'>
* <li>
* {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource.
@@ -522,7 +529,7 @@ public final class RestCall extends BeanSession implements Closeable {
* @return This object (for method chaining).
* @throws RestCallException If a retry was attempted, but the entity was not repeatable.
*/
- public RestCall input(final Object input) throws RestCallException {
+ public RestCall body(Object input) throws RestCallException {
this.input = input;
this.hasInput = true;
this.formData = null;
@@ -530,6 +537,32 @@ public final class RestCall extends BeanSession implements Closeable {
}
/**
+ * Same as {@link #body(Object)} but allows you to specify a part serializer to use to serialize the body.
+ *
+ * @param input
+ * The input to be sent to the REST resource (only valid for PUT and POST) requests. <br>
+ * @param partSerializer
+ * The part serializer to use to serialize the body of the request.
+ * @param schema
+ * The schema information about the part being serialized.
+ * @return This object (for method chaining).
+ * @throws RestCallException
+ */
+ public RestCall body(Object input, HttpPartSerializer partSerializer, HttpPartSchema schema) throws RestCallException {
+ try {
+ if (partSerializer != null)
+ body(partSerializer.serialize(HttpPartType.BODY, schema, input));
+ else
+ body(input);
+ } catch (SchemaValidationException e) {
+ throw new RestCallException(e, "Validation error on request body.");
+ } catch (SerializeException e) {
+ throw new RestCallException(e, "Serialization error on request body.");
+ }
+ return this;
+ }
+
+ /**
* Specifies the serializer to use on this call.
*
* <p>
@@ -557,7 +590,6 @@ public final class RestCall extends BeanSession implements Closeable {
return this;
}
-
//--------------------------------------------------------------------------------
// HTTP headers
//--------------------------------------------------------------------------------
@@ -584,12 +616,15 @@ public final class RestCall extends BeanSession implements Closeable {
public RestCall header(String name, Object value, boolean skipIfEmpty, HttpPartSerializer serializer, HttpPartSchema schema) throws RestCallException {
if (serializer == null)
serializer = client.getPartSerializer();
- if (! ("*".equals(name) || isEmpty(name))) {
+ boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairs;
+ if (! isMulti) {
if (value != null && ! (ObjectUtils.isEmpty(value) && skipIfEmpty))
try {
request.setHeader(name, serializer.createSession(null).serialize(HttpPartType.HEADER, schema, value));
- } catch (SerializeException | SchemaValidationException e) {
- throw new RestCallException(e);
+ } catch (SchemaValidationException e) {
+ throw new RestCallException(e, "Validation error on request header parameter ''{0}''=''{1}''", name, value);
+ } catch (SerializeException e) {
+ throw new RestCallException(e, "Serialization error on request header parameter ''{0}''", name);
}
} else if (value instanceof NameValuePairs) {
for (NameValuePair p : (NameValuePairs)value)
@@ -1419,16 +1454,6 @@ public final class RestCall extends BeanSession implements Closeable {
}
/**
- * @return The HTTP response code.
- * @throws RestCallException
- * @deprecated Use {@link #run()}.
- */
- @Deprecated
- public int execute() throws RestCallException {
- return run();
- }
-
- /**
* Method used to execute an HTTP response where you're only interested in the HTTP response code.
*
* <p>
@@ -1882,7 +1907,7 @@ public final class RestCall extends BeanSession implements Closeable {
BeanContext bc = parser;
if (bc == null)
bc = BeanContext.DEFAULT;
- return getResponse(bc.getClassMeta(type));
+ return getResponseInner(null, null, bc.getClassMeta(type));
}
/**
@@ -1975,7 +2000,37 @@ public final class RestCall extends BeanSession implements Closeable {
BeanContext bc = parser;
if (bc == null)
bc = BeanContext.DEFAULT;
- return (T)getResponse(bc.getClassMeta(type, args));
+ return (T)getResponseInner(null, null, bc.getClassMeta(type, args));
+ }
+
+ /**
+ * Same as {@link #getResponse(Type, Type...)} but allows you to specify a part parser to use for parsing the response.
+ *
+ * @param <T> The class type of the object to create.
+ * @param partParser
+ * The part parser.
+ * <br>Can be <jk>null</jk>.
+ * @param schema
+ * The schema information about the body of the response.
+ * <br>Can be <jk>null</jk>.
+ * @param type
+ * The object type to create.
+ * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+ * @param args
+ * The type arguments of the class if it's a collection or map.
+ * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+ * <br>Ignored if the main type is not a map or collection.
+ * @return The parsed object.
+ * @throws ParseException
+ * If the input contains a syntax error or is malformed, or is not valid for the specified type.
+ * @throws IOException If a connection error occurred.
+ * @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections.
+ */
+ public <T> T getResponse(HttpPartParser partParser, HttpPartSchema schema, Type type, Type...args) throws IOException, ParseException {
+ BeanContext bc = parser;
+ if (bc == null)
+ bc = BeanContext.DEFAULT;
+ return (T)getResponseInner(partParser, schema, bc.getClassMeta(type, args));
}
/**
@@ -2039,8 +2094,11 @@ public final class RestCall extends BeanSession implements Closeable {
return getResponsePojoRest(ObjectMap.class);
}
- <T> T getResponse(ClassMeta<T> type) throws IOException, ParseException {
+ <T> T getResponseInner(HttpPartParser partParser, HttpPartSchema schema, ClassMeta<T> type) throws IOException, ParseException {
try {
+ if (partParser == null)
+ partParser = this.partParser;
+
Class<?> ic = type.getInnerClass();
if (ic.equals(HttpResponse.class))
@@ -2050,6 +2108,17 @@ public final class RestCall extends BeanSession implements Closeable {
if (ic.equals(InputStream.class))
return (T)getInputStream();
+ connect();
+ Header h = response.getFirstHeader("Content-Type");
+ MediaType mt = MediaType.forString(h == null ? null : h.getValue());
+
+ if ((isEmpty(mt) || mt.toString().equals("text/plain"))) {
+ if (type.hasStringTransform())
+ return type.getStringTransform().transform(getResponseAsString());
+ if (partParser != null)
+ return partParser.createSession(null).parse(HttpPartType.BODY, schema, getResponseAsString(), type);
+ }
+
if (parser != null) {
try (Closeable in = parser.isReaderParser() ? getReader() : getInputStream()) {
return parser.parse(in, type);
@@ -2062,10 +2131,6 @@ public final class RestCall extends BeanSession implements Closeable {
if (type.hasInputStreamTransform())
return type.getInputStreamTransform().transform(getInputStream());
- MediaType mt = getMediaType();
- if ((isEmpty(mt) || mt.toString().equals("text/plain")) && type.hasStringTransform())
- return type.getStringTransform().transform(getResponseAsString());
-
throw new ParseException(
"Unsupported media-type in request header ''Content-Type'': ''{0}''\n\tSupported media-types: {1}",
getResponseHeader("Content-Type"), parser == null ? null : parser.getMediaTypes()
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
index 4801bc4..e5bf627 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
@@ -94,7 +94,7 @@ public final class RestCallException extends IOException {
* @throws IOException
*/
public RestCallException(String msg, HttpResponse response) throws ParseException, IOException {
- super(format("{0}\n{1}\nstatus='{2}'\nResponse: \n{3}", msg, response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), UTF8)));
+ super(format("{0}\n{1}\nstatus=''{2}''\nResponse: \n{3}", msg, response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), UTF8)));
}
/**
@@ -107,7 +107,7 @@ public final class RestCallException extends IOException {
* @param response The response from the server.
*/
public RestCallException(int responseCode, String responseMsg, String method, URI url, String response) {
- super(format("HTTP method '{0}' call to '{1}' caused response code '{2},{3}'.\nResponse: \n{4}", method, url, responseCode, responseMsg, response));
+ super(format("HTTP method ''{0}'' call to ''{1}'' caused response code ''{2}, {3}''.\nResponse: \n{4}", method, url, responseCode, responseMsg, response));
this.responseCode = responseCode;
this.responseStatusMessage = responseMsg;
this.response = response;
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 1d44d8c..37ca742 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -14,6 +14,8 @@ package org.apache.juneau.rest.client;
import static org.apache.juneau.internal.ReflectionUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.httppart.HttpPartType.*;
+import static org.apache.juneau.remoteable.ReturnValue.*;
import java.io.*;
import java.lang.reflect.*;
@@ -29,7 +31,6 @@ import org.apache.http.client.utils.*;
import org.apache.http.entity.*;
import org.apache.http.impl.client.*;
import org.apache.juneau.*;
-import org.apache.juneau.http.annotation.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
@@ -231,13 +232,34 @@ public class RestClient extends BeanContext implements Closeable {
public static final String RESTCLIENT_parser = PREFIX + "parser.o";
/**
+ * Configuration property: Part parser.
+ *
+ * <h5 class='section'>Property:</h5>
+ * <ul>
+ * <li><b>Name:</b> <js>"RestClient.partParser.o"</js>
+ * <li><b>Data type:</b> <code>Class<? <jk>implements</jk> HttpPartParser></code> or {@link HttpPartParser}.
+ * <li><b>Default:</b> {@link OpenApiPartParser};
+ * <li><b>Methods:</b>
+ * <ul>
+ * <li class='jm'>{@link RestClientBuilder#partParser(Class)}
+ * <li class='jm'>{@link RestClientBuilder#partParser(HttpPartParser)}
+ * </ul>
+ * </ul>
+ *
+ * <h5 class='section'>Description:</h5>
+ * <p>
+ * The parser to use for parsing POJOs from form data, query parameters, headers, and path variables.
+ */
+ public static final String RESTCLIENT_partParser = PREFIX + "partParser.o";
+
+ /**
* Configuration property: Part serializer.
*
* <h5 class='section'>Property:</h5>
* <ul>
- * <li><b>Name:</b> <js>"RestClient.urlEncodingSerializer.o"</js>
+ * <li><b>Name:</b> <js>"RestClient.partSerializer.o"</js>
* <li><b>Data type:</b> <code>Class<? <jk>implements</jk> HttpPartSerializer></code> or {@link HttpPartSerializer}.
- * <li><b>Default:</b> {@link SimpleUonPartSerializer};
+ * <li><b>Default:</b> {@link OpenApiPartSerializer};
* <li><b>Methods:</b>
* <ul>
* <li class='jm'>{@link RestClientBuilder#partSerializer(Class)}
@@ -385,6 +407,7 @@ public class RestClient extends BeanContext implements Closeable {
private final boolean keepHttpClientOpen, debug;
private final UrlEncodingSerializer urlEncodingSerializer; // Used for form posts only.
private final HttpPartSerializer partSerializer;
+ private final HttpPartParser partParser;
private final String rootUrl;
private volatile boolean isClosed = false;
private final StackTraceElement[] creationStack;
@@ -481,7 +504,8 @@ public class RestClient extends BeanContext implements Closeable {
}
this.urlEncodingSerializer = new SerializerBuilder(ps).build(UrlEncodingSerializer.class);
- this.partSerializer = getInstanceProperty(RESTCLIENT_partSerializer, HttpPartSerializer.class, SimpleUonPartSerializer.class, true, ps);
+ this.partSerializer = getInstanceProperty(RESTCLIENT_partSerializer, HttpPartSerializer.class, OpenApiPartSerializer.class, true, ps);
+ this.partParser = getInstanceProperty(RESTCLIENT_partParser, HttpPartParser.class, OpenApiPartParser.class, true, ps);
this.executorService = getInstanceProperty(RESTCLIENT_executorService, ExecutorService.class, null);
RestCallInterceptor[] rci = getInstanceArrayProperty(RESTCLIENT_interceptors, RestCallInterceptor.class, new RestCallInterceptor[0]);
@@ -584,14 +608,14 @@ public class RestClient extends BeanContext implements Closeable {
* @throws RestCallException If any authentication errors occurred.
*/
public RestCall doPut(Object url, Object o) throws RestCallException {
- return doCall("PUT", url, true).input(o);
+ return doCall("PUT", url, true).body(o);
}
/**
* Same as {@link #doPut(Object, Object)} but don't specify the input yet.
*
* <p>
- * You must call either {@link RestCall#input(Object)} or {@link RestCall#formData(String, Object)}
+ * You must call either {@link RestCall#body(Object)} or {@link RestCall#formData(String, Object)}
* to set the contents on the result object.
*
* @param url
@@ -636,14 +660,14 @@ public class RestClient extends BeanContext implements Closeable {
* @throws RestCallException If any authentication errors occurred.
*/
public RestCall doPost(Object url, Object o) throws RestCallException {
- return doCall("POST", url, true).input(o);
+ return doCall("POST", url, true).body(o);
}
/**
* Same as {@link #doPost(Object, Object)} but don't specify the input yet.
*
* <p>
- * You must call either {@link RestCall#input(Object)} or {@link RestCall#formData(String, Object)} to set the
+ * You must call either {@link RestCall#body(Object)} or {@link RestCall#formData(String, Object)} to set the
* contents on the result object.
*
* <h5 class='section'>Notes:</h5>
@@ -710,7 +734,7 @@ public class RestClient extends BeanContext implements Closeable {
*/
public RestCall doFormPost(Object url, Object o) throws RestCallException {
return doCall("POST", url, true)
- .input(o instanceof HttpEntity ? o : new RestRequestEntity(o, urlEncodingSerializer));
+ .body(o instanceof HttpEntity ? o : new RestRequestEntity(o, urlEncodingSerializer));
}
/**
@@ -774,7 +798,7 @@ public class RestClient extends BeanContext implements Closeable {
if (method != null && uri != null) {
rc = doCall(method, uri, content != null);
if (content != null)
- rc.input(new StringEntity(content));
+ rc.body(new StringEntity(content));
if (h != null)
for (Map.Entry<String,Object> e : h.entrySet())
rc.header(e.getKey(), e.getValue());
@@ -818,7 +842,7 @@ public class RestClient extends BeanContext implements Closeable {
public RestCall doCall(HttpMethod method, Object url, Object content) throws RestCallException {
RestCall rc = doCall(method.name(), url, method.hasContent());
if (method.hasContent())
- rc.input(content);
+ rc.body(content);
return rc;
}
@@ -1015,45 +1039,45 @@ public class RestClient extends BeanContext implements Closeable {
rc.serializer(serializer).parser(parser);
for (RemoteMethodArg a : rmm.getPathArgs())
- rc.path(a.name, args[a.index], a.serializer, null);
+ rc.path(a.getName(), args[a.getIndex()], a.getSerializer(), a.getSchema());
for (RemoteMethodArg a : rmm.getQueryArgs())
- rc.query(a.name, args[a.index], a.skipIfNE, a.serializer, null);
+ rc.query(a.getName(), args[a.getIndex()], a.isSkipIfEmpty(), a.getSerializer(), a.getSchema());
for (RemoteMethodArg a : rmm.getFormDataArgs())
- rc.formData(a.name, args[a.index], a.skipIfNE, a.serializer, null);
+ rc.formData(a.getName(), args[a.getIndex()], a.isSkipIfEmpty(), a.getSerializer(), a.getSchema());
for (RemoteMethodArg a : rmm.getHeaderArgs())
- rc.header(a.name, args[a.index], a.skipIfNE, a.serializer, null);
+ rc.header(a.getName(), args[a.getIndex()], a.isSkipIfEmpty(), a.getSerializer(), a.getSchema());
- if (rmm.getBodyArg() != null)
- rc.input(args[rmm.getBodyArg()]);
+ RemoteMethodArg ba = rmm.getBodyArg();
+ if (ba != null)
+ rc.body(args[ba.getIndex()], ba.getSerializer(), ba.getSchema());
if (rmm.getRequestBeanArgs().length > 0) {
BeanSession bs = createBeanSession();
- for (RemoteMethodArg rma : rmm.getRequestBeanArgs()) {
- BeanMap<?> bm = bs.toBeanMap(args[rma.index]);
+ for (RemoteMethodBeanArg rmba : rmm.getRequestBeanArgs()) {
+ BeanMap<?> bm = bs.toBeanMap(args[rmba.getIndex()]);
for (BeanPropertyValue bpv : bm.getValues(false)) {
BeanPropertyMeta pMeta = bpv.getMeta();
Object val = bpv.getValue();
-
- Path p = pMeta.getAnnotation(Path.class);
- if (p != null)
- rc.path(getName(p.name(), p.value(), pMeta), val, getPartSerializer(p.serializer(), rma.serializer), null);
-
- if (val != null) {
- Query q1 = pMeta.getAnnotation(Query.class);
- if (q1 != null)
- rc.query(getName(q1.name(), q1.value(), pMeta), val, q1.skipIfEmpty(), getPartSerializer(q1.serializer(), rma.serializer), null);
-
- FormData f1 = pMeta.getAnnotation(FormData.class);
- if (f1 != null)
- rc.formData(getName(f1.name(), f1.value(), pMeta), val, f1.skipIfEmpty(), getPartSerializer(f1.serializer(), rma.serializer), null);
-
- org.apache.juneau.http.annotation.Header h1 = pMeta.getAnnotation(org.apache.juneau.http.annotation.Header.class);
- if (h1 != null)
- rc.header(getName(h1.name(), h1.value(), pMeta), val, h1.skipIfEmpty(), getPartSerializer(h1.serializer(), rma.serializer), null);
+ RemoteMethodArg a = rmba.getProperty(pMeta.getName());
+ if (a != null) {
+ HttpPartType pt = a.getPartType();
+ if (pt == PATH)
+ rc.path(a.getName(), val, ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), a.getSchema());
+ if (val != null) {
+ if (pt == QUERY) {
+ rc.query(a.getName(), val, a.isSkipIfEmpty(), ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), a.getSchema());
+ } else if (pt == FORMDATA) {
+ rc.formData(a.getName(), val, a.isSkipIfEmpty(), ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), a.getSchema());
+ } else if (pt == HEADER) {
+ rc.header(a.getName(), val, a.isSkipIfEmpty(), ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), a.getSchema());
+ } else if (pt == HttpPartType.BODY) {
+ rc.body(val, ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), a.getSchema());
+ }
+ }
}
}
}
@@ -1062,12 +1086,16 @@ public class RestClient extends BeanContext implements Closeable {
if (rmm.getOtherArgs().length > 0) {
Object[] otherArgs = new Object[rmm.getOtherArgs().length];
int i = 0;
- for (Integer otherArg : rmm.getOtherArgs())
- otherArgs[i++] = args[otherArg];
- rc.input(otherArgs);
+ for (RemoteMethodArg a : rmm.getOtherArgs())
+ otherArgs[i++] = args[a.getIndex()];
+ rc.body(otherArgs);
}
- if (rmm.getReturns() == ReturnValue.HTTP_STATUS) {
+ RemoteMethodReturn rmr = rmm.getReturns();
+ if (rmr.getReturnValue() == NONE) {
+ rc.run();
+ return null;
+ } else if (rmr.getReturnValue() == HTTP_STATUS) {
rc.ignoreErrors();
int returnCode = rc.run();
Class<?> rt = method.getReturnType();
@@ -1076,13 +1104,13 @@ public class RestClient extends BeanContext implements Closeable {
if (rt == Boolean.class || rt == boolean.class)
return returnCode < 400;
throw new RestCallException("Invalid return type on method annotated with @RemoteableMethod(returns=HTTP_STATUS). Only integer and booleans types are valid.");
+ } else {
+ Object v = rc.getResponse(rmr.getParser(), rmr.getSchema(), method.getGenericReturnType());
+ if (v == null && method.getReturnType().isPrimitive())
+ v = ClassUtils.getPrimitiveDefault(method.getReturnType());
+ return v;
}
- Object v = rc.getResponse(method.getGenericReturnType());
- if (v == null && method.getReturnType().isPrimitive())
- v = ClassUtils.getPrimitiveDefault(method.getReturnType());
- return v;
-
} catch (RestCallException e) {
// Try to throw original exception if possible.
e.throwServerException(interfaceClass.getClassLoader());
@@ -1126,6 +1154,10 @@ public class RestClient extends BeanContext implements Closeable {
return partSerializer;
}
+ HttpPartParser getPartParser() {
+ return partParser;
+ }
+
URI toURI(Object url) throws URISyntaxException {
if (url instanceof URI)
return (URI)url;
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
index 0ec6db9..4c0b45b 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
@@ -1113,6 +1113,46 @@ public class RestClientBuilder extends BeanContextBuilder {
}
/**
+ * Configuration property: Part parser.
+ *
+ * <p>
+ * The parser to use for parsing POJOs from form data, query parameters, headers, and path variables.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * <li class='jf'>{@link RestClient#RESTCLIENT_partParser}
+ * </ul>
+ *
+ * @param value
+ * The new value for this setting.
+ * <br>The default value is {@link OpenApiPartParser}.
+ * @return This object (for method chaining).
+ */
+ public RestClientBuilder partParser(Class<? extends HttpPartParser> value) {
+ return set(RESTCLIENT_partParser, value);
+ }
+
+ /**
+ * Configuration property: Part parser.
+ *
+ * <p>
+ * Same as {@link #partParser(Class)} but takes in a parser instance.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * <li class='jf'>{@link RestClient#RESTCLIENT_partParser}
+ * </ul>
+ *
+ * @param value
+ * The new value for this setting.
+ * <br>The default value is {@link OpenApiPartParser}.
+ * @return This object (for method chaining).
+ */
+ public RestClientBuilder partParser(HttpPartParser value) {
+ return set(RESTCLIENT_partParser, value);
+ }
+
+ /**
* Configuration property: Part serializer.
*
* <p>
@@ -1125,7 +1165,7 @@ public class RestClientBuilder extends BeanContextBuilder {
*
* @param value
* The new value for this setting.
- * <br>The default value is {@link SimpleUonPartSerializer}.
+ * <br>The default value is {@link OpenApiPartSerializer}.
* @return This object (for method chaining).
*/
public RestClientBuilder partSerializer(Class<? extends HttpPartSerializer> value) {
@@ -1145,7 +1185,7 @@ public class RestClientBuilder extends BeanContextBuilder {
*
* @param value
* The new value for this setting.
- * <br>The default value is {@link SimpleUonPartSerializer}.
+ * <br>The default value is {@link OpenApiPartSerializer}.
* @return This object (for method chaining).
*/
public RestClientBuilder partSerializer(HttpPartSerializer value) {
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/SerializedNameValuePair.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/SerializedNameValuePair.java
index 5adca48..1b4fb8c 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/SerializedNameValuePair.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/SerializedNameValuePair.java
@@ -13,6 +13,7 @@
package org.apache.juneau.rest.client;
import org.apache.http.*;
+import org.apache.juneau.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.urlencoding.*;
@@ -64,8 +65,10 @@ public final class SerializedNameValuePair implements NameValuePair {
public String getValue() {
try {
return serializer.createSession(null).serialize(HttpPartType.FORMDATA, schema, value);
- } catch (SerializeException | SchemaValidationException e) {
- throw new RuntimeException(e);
+ } catch (SchemaValidationException e) {
+ throw new FormattedRuntimeException(e, "Validation error on request form-data parameter ''{0}''=''{1}''", name, value);
+ } catch (SerializeException e) {
+ throw new FormattedRuntimeException(e, "Serialization error on request form-data parameter ''{0}''", name);
}
}
}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestLogger.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestLogger.java
index c76d33a..918c4ea 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestLogger.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestLogger.java
@@ -52,6 +52,11 @@ public class BasicRestLogger implements RestLogger {
return logger;
}
+ @Override /* RestLogger */
+ public void setLevel(Level level) {
+ getLogger().setLevel(level);
+ }
+
/**
* Log a message to the logger.
*
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index eb74228..857a332 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -26,6 +26,7 @@ import java.nio.charset.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
+import java.util.logging.*;
import javax.activation.*;
import javax.servlet.*;
@@ -2925,6 +2926,8 @@ public final class RestContext extends BeanContext {
staticFileResponseHeaders = getMapProperty(REST_staticFileResponseHeaders, Object.class);
logger = getInstanceProperty(REST_logger, resource, RestLogger.class, NoOpRestLogger.class, true, this);
+ if (debug)
+ logger.setLevel(Level.FINE);
varResolver = builder.varResolverBuilder
.vars(
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestLogger.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestLogger.java
index ad2a4c9..0f0f074 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestLogger.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestLogger.java
@@ -40,6 +40,13 @@ public interface RestLogger {
public interface Null extends RestLogger {}
/**
+ * Sets the logging level for this logger.
+ *
+ * @param level The new level.
+ */
+ public void setLevel(Level level);
+
+ /**
* Log a message to the logger.
*
* @param level The log level.