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 <jamesbog...@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.